forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvisualstudio_py_debugger.py
More file actions
2714 lines (2295 loc) · 101 KB
/
visualstudio_py_debugger.py
File metadata and controls
2714 lines (2295 loc) · 101 KB
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
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Python Tools for Visual Studio
# Copyright(c) Microsoft Corporation
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the License); you may not use
# this file except in compliance with the License. You may obtain a copy of the
# License at http://www.apache.org/licenses/LICENSE-2.0
#
# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
# IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
# MERCHANTABLITY OR NON-INFRINGEMENT.
#
# See the Apache Version 2.0 License for specific language governing
# permissions and limitations under the License.
# With number of modifications by Don Jayamanne
from __future__ import with_statement
__version__ = "3.0.0.0"
# This module MUST NOT import threading in global scope. This is because in a direct (non-ptvsd)
# attach scenario, it is loaded on the injected debugger attach thread, and if threading module
# hasn't been loaded already, it will assume that the thread on which it is being loaded is the
# main thread. This will cause issues when the thread goes away after attach completes.
_threading = None
import sys
import ctypes
try:
import thread
except ImportError:
import _thread as thread
import socket
import struct
import weakref
import traceback
import types
import bisect
from os import path
import ntpath
import runpy
import datetime
from codecs import BOM_UTF8
try:
# In the local attach scenario, visualstudio_py_util is injected into globals()
# by PyDebugAttach before loading this module, and cannot be imported.
_vspu = visualstudio_py_util
except:
try:
import visualstudio_py_util as _vspu
except ImportError:
import ptvsd.visualstudio_py_util as _vspu
to_bytes = _vspu.to_bytes
exec_file = _vspu.exec_file
exec_module = _vspu.exec_module
exec_code = _vspu.exec_code
read_bytes = _vspu.read_bytes
read_int = _vspu.read_int
read_string = _vspu.read_string
write_bytes = _vspu.write_bytes
write_int = _vspu.write_int
write_string = _vspu.write_string
safe_repr = _vspu.SafeRepr()
try:
# In the local attach scenario, visualstudio_py_repl is injected into globals()
# by PyDebugAttach before loading this module, and cannot be imported.
_vspr = visualstudio_py_repl
except:
try:
import visualstudio_py_repl as _vspr
except ImportError:
import ptvsd.visualstudio_py_repl as _vspr
try:
import stackless
except ImportError:
stackless = None
try:
xrange
except:
xrange = range
if sys.platform == 'cli':
import clr
from System.Runtime.CompilerServices import ConditionalWeakTable
IPY_SEEN_MODULES = ConditionalWeakTable[object, object]()
# Import encodings early to avoid import on the debugger thread, which may cause deadlock
from encodings import utf_8
# WARNING: Avoid imports beyond this point, specifically on the debugger thread, as this may cause
# deadlock where the debugger thread performs an import while a user thread has the import lock
# save start_new_thread so we can call it later, we'll intercept others calls to it.
debugger_dll_handle = None
DETACHED = True
def thread_creator(func, args, kwargs = {}, *extra_args):
if not isinstance(args, tuple):
# args is not a tuple. This may be because we have become bound to a
# class, which has offset our arguments by one.
if isinstance(kwargs, tuple):
func, args = args, kwargs
kwargs = extra_args[0] if len(extra_args) > 0 else {}
return _start_new_thread(new_thread_wrapper, (func, args, kwargs))
_start_new_thread = thread.start_new_thread
THREADS = {}
THREADS_LOCK = thread.allocate_lock()
MODULES = []
BREAK_ON_SYSTEMEXIT_ZERO = False
DEBUG_STDLIB = False
DJANGO_DEBUG = False
IGNORE_DJANGO_TEMPLATE_WARNINGS = False
# Py3k compat - alias unicode to str
try:
unicode
except:
unicode = str
# A value of a synthesized child. The string is passed through to the variable list, and type is not displayed at all.
class SynthesizedValue(object):
def __init__(self, repr_value='', len_value=None):
self.repr_value = repr_value
self.len_value = len_value
def __repr__(self):
return self.repr_value
def __len__(self):
return self.len_value
# Specifies list of files not to debug. Can be extended by other modules
# (the REPL does this for $attach support and not stepping into the REPL).
DONT_DEBUG = [path.normcase(__file__), path.normcase(_vspu.__file__)]
if sys.version_info >= (3, 3):
DONT_DEBUG.append(path.normcase('<frozen importlib._bootstrap>'))
if sys.version_info >= (3, 5):
DONT_DEBUG.append(path.normcase('<frozen importlib._bootstrap_external>'))
# Contains information about all breakpoints in the process. Keys are line numbers on which
# there are breakpoints in any file, and values are dicts. For every line number, the
# corresponding dict contains all the breakpoints that fall on that line. The keys in that
# dict are tuples of the form (filename, breakpoint_id), each entry representing a single
# breakpoint, and values are BreakpointInfo objects.
#
# For example, given the following breakpoints:
#
# 1. In 'main.py' at line 10.
# 2. In 'main.py' at line 20.
# 3. In 'module.py' at line 10.
#
# the contents of BREAKPOINTS would be:
# {10: {('main.py', 1): ..., ('module.py', 3): ...}, 20: {('main.py', 2): ... }}
BREAKPOINTS = {}
# Contains information about all pending (i.e. not yet bound) breakpoints in the process.
# Elements are BreakpointInfo objects.
PENDING_BREAKPOINTS = set()
# Must be in sync with enum PythonBreakpointConditionKind in PythonBreakpoint.cs
BREAKPOINT_CONDITION_ALWAYS = 0
BREAKPOINT_CONDITION_WHEN_TRUE = 1
BREAKPOINT_CONDITION_WHEN_CHANGED = 2
# Must be in sync with enum PythonBreakpointPassCountKind in PythonBreakpoint.cs
BREAKPOINT_PASS_COUNT_ALWAYS = 0
BREAKPOINT_PASS_COUNT_EVERY = 1
BREAKPOINT_PASS_COUNT_WHEN_EQUAL = 2
BREAKPOINT_PASS_COUNT_WHEN_EQUAL_OR_GREATER = 3
## Begin modification by Don Jayamanne
DJANGO_VERSIONS_IDENTIFIED = False
IS_DJANGO18 = False
IS_DJANGO19 = False
IS_DJANGO19_OR_HIGHER = False
try:
dict_contains = dict.has_key
except:
try:
#Py3k does not have has_key anymore, and older versions don't have __contains__
dict_contains = dict.__contains__
except:
try:
dict_contains = dict.has_key
except NameError:
def dict_contains(d, key):
return d.has_key(key)
## End modification by Don Jayamanne
class BreakpointInfo(object):
__slots__ = [
'breakpoint_id', 'filename', 'lineno', 'condition_kind', 'condition',
'pass_count_kind', 'pass_count', 'is_bound', 'last_condition_value',
'hit_count'
]
# For "when changed" breakpoints, this is used as the initial value of last_condition_value,
# such that it is guaranteed to not compare equal to any other value that it will get later.
_DUMMY_LAST_VALUE = object()
def __init__(self, breakpoint_id, filename, lineno, condition_kind, condition, pass_count_kind, pass_count):
self.breakpoint_id = breakpoint_id
self.filename = filename
self.lineno = lineno
self.condition_kind = condition_kind
self.condition = condition
self.pass_count_kind = pass_count_kind
self.pass_count = pass_count
self.is_bound = False
self.last_condition_value = BreakpointInfo._DUMMY_LAST_VALUE
self.hit_count = 0
@staticmethod
def find_by_id(breakpoint_id):
for line, bp_dict in BREAKPOINTS.items():
for (filename, bp_id), bp in bp_dict.items():
if bp_id == breakpoint_id:
return bp
return None
# lock for calling .send on the socket
send_lock = thread.allocate_lock()
class _SendLockContextManager(object):
"""context manager for send lock. Handles both acquiring/releasing the
send lock as well as detaching the debugger if the remote process
is disconnected"""
def __enter__(self):
# mark that we're about to do socket I/O so we won't deliver
# debug events when we're debugging the standard library
cur_thread = get_thread_from_id(thread.get_ident())
if cur_thread is not None:
cur_thread.is_sending = True
send_lock.acquire()
def __exit__(self, exc_type, exc_value, tb):
send_lock.release()
# start sending debug events again
cur_thread = get_thread_from_id(thread.get_ident())
if cur_thread is not None:
cur_thread.is_sending = False
if exc_type is not None:
detach_threads()
detach_process()
# swallow the exception, we're no longer debugging
return True
_SendLockCtx = _SendLockContextManager()
SEND_BREAK_COMPLETE = False
STEPPING_OUT = -1 # first value, we decrement below this
STEPPING_NONE = 0
STEPPING_BREAK = 1
STEPPING_LAUNCH_BREAK = 2
STEPPING_ATTACH_BREAK = 3
STEPPING_INTO = 4
STEPPING_OVER = 5 # last value, we increment past this.
USER_STEPPING = (STEPPING_OUT, STEPPING_INTO, STEPPING_OVER)
FRAME_KIND_NONE = 0
FRAME_KIND_PYTHON = 1
FRAME_KIND_DJANGO = 2
DJANGO_BUILTINS = {'True': True, 'False': False, 'None': None}
PYTHON_EVALUATION_RESULT_REPR_KIND_NORMAL = 0 # regular repr and hex repr (if applicable) for the evaluation result; length is len(result)
PYTHON_EVALUATION_RESULT_REPR_KIND_RAW = 1 # repr is raw representation of the value - see TYPES_WITH_RAW_REPR; length is len(repr)
PYTHON_EVALUATION_RESULT_REPR_KIND_RAWLEN = 2 # same as above, but only the length is reported, not the actual value
PYTHON_EVALUATION_RESULT_EXPANDABLE = 1
PYTHON_EVALUATION_RESULT_METHOD_CALL = 2
PYTHON_EVALUATION_RESULT_SIDE_EFFECTS = 4
PYTHON_EVALUATION_RESULT_RAW = 8
PYTHON_EVALUATION_RESULT_HAS_RAW_REPR = 16
# Don't show attributes of these types if they come from the class (assume they are methods).
METHOD_TYPES = (
types.FunctionType,
types.MethodType,
types.BuiltinFunctionType,
type("".__repr__), # method-wrapper
)
# repr() for these types can be used as input for eval() to get the original value.
# float is intentionally not included because it is not always round-trippable (e.g inf, nan).
TYPES_WITH_ROUND_TRIPPING_REPR = set((type(None), int, bool, str, unicode))
if sys.version[0] == '3':
TYPES_WITH_ROUND_TRIPPING_REPR.add(bytes)
else:
TYPES_WITH_ROUND_TRIPPING_REPR.add(long)
# repr() for these types can be used as input for eval() to get the original value, provided that the same is true for all their elements.
COLLECTION_TYPES_WITH_ROUND_TRIPPING_REPR = set((tuple, list, set, frozenset))
# eval(repr(x)), but optimized for common types for which it is known that result == x.
def eval_repr(x):
def is_repr_round_tripping(x):
# Do exact type checks here - subclasses can override __repr__.
if type(x) in TYPES_WITH_ROUND_TRIPPING_REPR:
return True
elif type(x) in COLLECTION_TYPES_WITH_ROUND_TRIPPING_REPR:
# All standard sequence types are round-trippable if their elements are.
return all((is_repr_round_tripping(item) for item in x))
else:
return False
if is_repr_round_tripping(x):
return x
else:
return eval(repr(x), {})
# key is type, value is function producing the raw repr
TYPES_WITH_RAW_REPR = {
unicode: (lambda s: s)
}
# bytearray is 2.6+
try:
# getfilesystemencoding is used here because it effectively corresponds to the notion of "locale encoding":
# current ANSI codepage on Windows, LC_CTYPE on Linux, UTF-8 on OS X - which is exactly what we want.
TYPES_WITH_RAW_REPR[bytearray] = lambda b: b.decode(sys.getfilesystemencoding(), 'ignore')
except:
pass
if sys.version[0] == '3':
TYPES_WITH_RAW_REPR[bytes] = TYPES_WITH_RAW_REPR[bytearray]
else:
TYPES_WITH_RAW_REPR[str] = TYPES_WITH_RAW_REPR[unicode]
if sys.version[0] == '3':
# work around a crashing bug on CPython 3.x where they take a hard stack overflow
# we'll never see this exception but it'll allow us to keep our try/except handler
# the same across all versions of Python
class StackOverflowException(Exception): pass
else:
StackOverflowException = RuntimeError
ASBR = to_bytes('ASBR')
SETL = to_bytes('SETL')
THRF = to_bytes('THRF')
DETC = to_bytes('DETC')
NEWT = to_bytes('NEWT')
EXTT = to_bytes('EXTT')
EXIT = to_bytes('EXIT')
EXCP = to_bytes('EXCP')
MODL = to_bytes('MODL')
STPD = to_bytes('STPD')
BRKS = to_bytes('BRKS')
BRKF = to_bytes('BRKF')
BRKH = to_bytes('BRKH')
BRKC = to_bytes('BRKC')
BKHC = to_bytes('BKHC')
LOAD = to_bytes('LOAD')
EXCE = to_bytes('EXCE')
EXCR = to_bytes('EXCR')
CHLD = to_bytes('CHLD')
OUTP = to_bytes('OUTP')
REQH = to_bytes('REQH')
LAST = to_bytes('LAST')
def get_thread_from_id(id):
THREADS_LOCK.acquire()
try:
return THREADS.get(id)
finally:
THREADS_LOCK.release()
def should_send_frame(frame):
return (frame is not None and
frame.f_code not in DEBUG_ENTRYPOINTS and
path.normcase(frame.f_code.co_filename) not in DONT_DEBUG)
KNOWN_DIRECTORIES = set((None, ''))
KNOWN_ZIPS = set()
def is_file_in_zip(filename):
parent, name = path.split(path.abspath(filename))
if parent in KNOWN_DIRECTORIES:
return False
elif parent in KNOWN_ZIPS:
return True
elif path.isdir(parent):
KNOWN_DIRECTORIES.add(parent)
return False
else:
KNOWN_ZIPS.add(parent)
return True
def lookup_builtin(name, frame):
try:
return frame.f_builtins.get(bits)
except:
# http://ironpython.codeplex.com/workitem/30908
builtins = frame.f_globals['__builtins__']
if not isinstance(builtins, dict):
builtins = builtins.__dict__
return builtins.get(name)
def lookup_local(frame, name):
bits = name.split('.')
obj = frame.f_locals.get(bits[0]) or frame.f_globals.get(bits[0]) or lookup_builtin(bits[0], frame)
bits.pop(0)
while bits and obj is not None and type(obj) is types.ModuleType:
obj = getattr(obj, bits.pop(0), None)
return obj
if sys.version_info[0] >= 3:
_EXCEPTIONS_MODULE = 'builtins'
else:
_EXCEPTIONS_MODULE = 'exceptions'
def get_exception_name(exc_type):
if exc_type.__module__ == _EXCEPTIONS_MODULE:
return exc_type.__name__
else:
return exc_type.__module__ + '.' + exc_type.__name__
# These constants come from Visual Studio - enum_EXCEPTION_STATE
BREAK_MODE_NEVER = 0
BREAK_MODE_ALWAYS = 1
BREAK_MODE_UNHANDLED = 32
BREAK_TYPE_NONE = 0
BREAK_TYPE_UNHANDLED = 1
BREAK_TYPE_HANDLED = 2
class ExceptionBreakInfo(object):
BUILT_IN_HANDLERS = {
path.normcase('<frozen importlib._bootstrap>'): ((None, None, '*'),),
path.normcase('build\\bdist.win32\\egg\\pkg_resources.py'): ((None, None, '*'),),
path.normcase('build\\bdist.win-amd64\\egg\\pkg_resources.py'): ((None, None, '*'),),
}
def __init__(self):
self.default_mode = BREAK_MODE_UNHANDLED
self.break_on = { }
self.handler_cache = dict(self.BUILT_IN_HANDLERS)
self.handler_lock = thread.allocate_lock()
self.add_exception('exceptions.IndexError', BREAK_MODE_NEVER)
self.add_exception('builtins.IndexError', BREAK_MODE_NEVER)
self.add_exception('exceptions.KeyError', BREAK_MODE_NEVER)
self.add_exception('builtins.KeyError', BREAK_MODE_NEVER)
self.add_exception('exceptions.AttributeError', BREAK_MODE_NEVER)
self.add_exception('builtins.AttributeError', BREAK_MODE_NEVER)
self.add_exception('exceptions.StopIteration', BREAK_MODE_NEVER)
self.add_exception('builtins.StopIteration', BREAK_MODE_NEVER)
self.add_exception('exceptions.GeneratorExit', BREAK_MODE_NEVER)
self.add_exception('builtins.GeneratorExit', BREAK_MODE_NEVER)
def clear(self):
self.default_mode = BREAK_MODE_UNHANDLED
self.break_on.clear()
self.handler_cache = dict(self.BUILT_IN_HANDLERS)
def should_break(self, thread, ex_type, ex_value, trace):
probe_stack()
name = get_exception_name(ex_type)
mode = self.break_on.get(name, self.default_mode)
break_type = BREAK_TYPE_NONE
if mode & BREAK_MODE_ALWAYS:
if self.is_handled(thread, ex_type, ex_value, trace):
break_type = BREAK_TYPE_HANDLED
else:
break_type = BREAK_TYPE_UNHANDLED
elif (mode & BREAK_MODE_UNHANDLED) and not self.is_handled(thread, ex_type, ex_value, trace):
break_type = BREAK_TYPE_UNHANDLED
if break_type:
if issubclass(ex_type, SystemExit):
if not BREAK_ON_SYSTEMEXIT_ZERO:
if not ex_value or (isinstance(ex_value, SystemExit) and not ex_value.code):
break_type = BREAK_TYPE_NONE
return break_type
def is_handled(self, thread, ex_type, ex_value, trace):
if trace is None:
# get out if we didn't get a traceback
return False
if trace.tb_next is not None:
if should_send_frame(trace.tb_next.tb_frame) and should_debug_code(trace.tb_next.tb_frame.f_code):
# don't break if this is not the top of the traceback,
# unless the previous frame was not debuggable
return True
cur_frame = trace.tb_frame
while should_send_frame(cur_frame) and cur_frame.f_code is not None and cur_frame.f_code.co_filename is not None:
filename = path.normcase(cur_frame.f_code.co_filename)
if is_file_in_zip(filename):
# File is in a zip, so assume it handles exceptions
return True
if not is_same_py_file(filename, __file__):
handlers = self.handler_cache.get(filename)
if handlers is None:
# req handlers for this file from the debug engine
self.handler_lock.acquire()
with _SendLockCtx:
write_bytes(conn, REQH)
write_string(conn, filename)
# wait for the handler data to be received
self.handler_lock.acquire()
self.handler_lock.release()
handlers = self.handler_cache.get(filename)
if handlers is None:
# no code available, so assume unhandled
return False
line = cur_frame.f_lineno
for line_start, line_end, expressions in handlers:
if line_start is None or line_start <= line < line_end:
if '*' in expressions:
return True
for text in expressions:
try:
res = lookup_local(cur_frame, text)
if res is not None and issubclass(ex_type, res):
return True
except:
pass
cur_frame = cur_frame.f_back
return False
def add_exception(self, name, mode=BREAK_MODE_UNHANDLED):
if name.startswith(_EXCEPTIONS_MODULE + '.'):
name = name[len(_EXCEPTIONS_MODULE) + 1:]
self.break_on[name] = mode
BREAK_ON = ExceptionBreakInfo()
def probe_stack(depth = 10):
"""helper to make sure we have enough stack space to proceed w/o corrupting
debugger state."""
if depth == 0:
return
probe_stack(depth - 1)
PREFIXES = [path.normcase(sys.prefix)]
# If we're running in a virtual env, DEBUG_STDLIB should respect this too.
if hasattr(sys, 'base_prefix'):
PREFIXES.append(path.normcase(sys.base_prefix))
if hasattr(sys, 'real_prefix'):
PREFIXES.append(path.normcase(sys.real_prefix))
def should_debug_code(code):
if not code or not code.co_filename:
return False
filename = path.normcase(code.co_filename)
if not DEBUG_STDLIB:
for prefix in PREFIXES:
if prefix != '' and filename.startswith(prefix):
return False
for dont_debug_file in DONT_DEBUG:
if is_same_py_file(filename, dont_debug_file):
return False
if is_file_in_zip(filename):
# file in inside an egg or zip, so we can't debug it
return False
return True
attach_lock = thread.allocate()
attach_sent_break = False
local_path_to_vs_path = {}
def breakpoint_path_match(vs_path, local_path):
vs_path_norm = path.normcase(vs_path)
local_path_norm = path.normcase(local_path)
if local_path_to_vs_path.get(local_path_norm) == vs_path_norm:
return True
# Walk the local filesystem from local_path up, matching agains win_path component by component,
# and stop when we no longer see an __init__.py. This should give a reasonably close approximation
# of matching the package name.
while True:
local_path, local_name = path.split(local_path)
vs_path, vs_name = ntpath.split(vs_path)
# Match the last component in the path. If one or both components are unavailable, then
# we have reached the root on the corresponding path without successfully matching.
if not local_name or not vs_name or path.normcase(local_name) != path.normcase(vs_name):
return False
# If we have an __init__.py, this module was inside the package, and we still need to match
# thatpackage, so walk up one level and keep matching. Otherwise, we've walked as far as we
# needed to, and matched all names on our way, so this is a match.
if not path.exists(path.join(local_path, '__init__.py')):
break
local_path_to_vs_path[local_path_norm] = vs_path_norm
return True
def update_all_thread_stacks(blocking_thread = None, check_is_blocked = True):
THREADS_LOCK.acquire()
all_threads = list(THREADS.values())
THREADS_LOCK.release()
for cur_thread in all_threads:
if cur_thread is blocking_thread:
continue
cur_thread._block_starting_lock.acquire()
if not check_is_blocked or not cur_thread._is_blocked:
# release the lock, we're going to run user code to evaluate the frames
cur_thread._block_starting_lock.release()
frames = cur_thread.get_frame_list()
# re-acquire the lock and make sure we're still not blocked. If so send
# the frame list.
cur_thread._block_starting_lock.acquire()
if not check_is_blocked or not cur_thread._is_blocked:
cur_thread.send_frame_list(frames)
cur_thread._block_starting_lock.release()
DJANGO_BREAKPOINTS = {}
DJANGO_TEMPLATES = {}
class DjangoBreakpointInfo(object):
def __init__(self, filename):
self._line_locations = None
self.filename = filename
self.breakpoints = {}
self.rangeIsPlainText = {}
def add_breakpoint(self, lineno, brkpt_id):
self.breakpoints[lineno] = brkpt_id
def remove_breakpoint(self, lineno):
try:
del self.breakpoints[lineno]
except:
pass
def has_breakpoint_for_line(self, lineNumber):
return self.breakpoints.get(lineNumber) is not None
def is_range_plain_text(self, start, end):
key = str(start) + '-' + str(end)
if self.rangeIsPlainText.get(key) is None:
# we need to calculate our line number offset information
try:
with open(self.filename, 'rb') as contents:
contents.seek(start, 0) # 0 = start of file, optional in this case
data = contents.read(end - start)
isPlainText = True
if data.startswith('{{') and data.endswith('}}'):
isPlainText = False
if data.startswith('{%') and data.endswith('%}'):
isPlainText = False
self.rangeIsPlainText[key] = isPlainText
return isPlainText
except:
return False
else:
return self.rangeIsPlainText.get(key)
def line_number_to_offset(self, lineNumber):
line_locs = self.line_locations
if line_locs is not None:
low_line = line_locs[lineNumber - 1]
hi_line = line_locs[lineNumber]
return low_line, hi_line
return (None, None)
@property
def line_locations(self):
if self._line_locations is None:
# we need to calculate our line number offset information
try:
contents = open(self.filename, 'rb')
except:
# file not available, locked, etc...
pass
else:
with contents:
line_info = []
file_len = 0
for line in contents:
line_len = len(line)
if not line_info and line.startswith(BOM_UTF8):
line_len -= len(BOM_UTF8) # Strip the BOM, Django seems to ignore this...
if line.endswith(to_bytes('\r\n')):
line_len -= 1 # Django normalizes newlines to \n
file_len += line_len
line_info.append(file_len)
contents.close()
self._line_locations = line_info
return self._line_locations
def get_line_range(self, start, end):
line_locs = self.line_locations
if line_locs is not None:
low_line = bisect.bisect_right(line_locs, start)
hi_line = bisect.bisect_right(line_locs, end)
return low_line, hi_line
return (None, None)
def should_break(self, start, end):
low_line, hi_line = self.get_line_range(start, end)
if low_line is not None and hi_line is not None:
# low_line/hi_line is 0 based, self.breakpoints is 1 based
for i in xrange(low_line+1, hi_line+2):
bkpt_id = self.breakpoints.get(i)
if bkpt_id is not None:
return True, bkpt_id
return False, 0
def get_django_frame_source(frame):
global DJANGO_VERSIONS_IDENTIFIED
global IS_DJANGO18
global IS_DJANGO19
global IS_DJANGO19_OR_HIGHER
if DJANGO_VERSIONS_IDENTIFIED == False:
DJANGO_VERSIONS_IDENTIFIED = True
try:
import django
version = django.VERSION
IS_DJANGO18 = version[0] == 1 and version[1] == 8
IS_DJANGO19 = version[0] == 1 and version[1] == 9
IS_DJANGO19_OR_HIGHER = ((version[0] == 1 and version[1] >= 9) or version[0] > 1)
except:
pass
if frame.f_code.co_name == 'render':
self_obj = frame.f_locals.get('self', None)
if self_obj is None:
return None
origin = _get_template_file_name(frame)
line = _get_template_line(frame)
position = None
if self_obj is not None and hasattr(self_obj, 'token') and hasattr(self_obj.token, 'position'):
position = self_obj.token.position
if origin is not None and position is None:
active_bps = DJANGO_BREAKPOINTS.get(origin.lower())
if active_bps is None:
active_bps = DJANGO_TEMPLATES.get(origin.lower())
if active_bps is None:
DJANGO_BREAKPOINTS[origin.lower()] = active_bps = DjangoBreakpointInfo(origin.lower())
if active_bps is not None:
if line is not None:
position = active_bps.line_number_to_offset(line)
if origin and position:
return str(origin), position, line
return None
class ModuleExitFrame(object):
def __init__(self, real_frame):
self.real_frame = real_frame
self.f_lineno = real_frame.f_lineno + 1
def __getattr__(self, name):
return getattr(self.real_frame, name)
class Thread(object):
def __init__(self, id = None):
if id is not None:
self.id = id
else:
self.id = thread.get_ident()
self._events = {'call' : self.handle_call,
'line' : self.handle_line,
'return' : self.handle_return,
'exception' : self.handle_exception,
'c_call' : self.handle_c_call,
'c_return' : self.handle_c_return,
'c_exception' : self.handle_c_exception,
}
self.cur_frame = None
self.stepping = STEPPING_NONE
self.unblock_work = None
self._block_lock = thread.allocate_lock()
self._block_lock.acquire()
self._block_starting_lock = thread.allocate_lock()
self._is_blocked = False
self._is_working = False
self.stopped_on_line = None
self.detach = False
self.trace_func = self.trace_func # replace self.trace_func w/ a bound method so we don't need to re-create these regularly
self.prev_trace_func = None
self.trace_func_stack = []
self.reported_process_loaded = False
self.django_stepping = None
self.is_sending = False
# stackless changes
if stackless is not None:
self._stackless_attach()
if sys.platform == 'cli':
self.frames = []
if sys.platform == 'cli':
# workaround an IronPython bug where we're sometimes missing the back frames
# http://ironpython.codeplex.com/workitem/31437
def push_frame(self, frame):
self.cur_frame = frame
self.frames.append(frame)
def pop_frame(self):
self.frames.pop()
self.cur_frame = self.frames[-1]
else:
def push_frame(self, frame):
self.cur_frame = frame
def pop_frame(self):
self.cur_frame = self.cur_frame.f_back
def _stackless_attach(self):
try:
stackless.tasklet.trace_function
except AttributeError:
# the tasklets need to be traced on a case by case basis
# sys.trace needs to be called within their calling context
def __call__(tsk, *args, **kwargs):
f = tsk.tempval
def new_f(old_f, args, kwargs):
sys.settrace(self.trace_func)
try:
if old_f is not None:
return old_f(*args, **kwargs)
finally:
sys.settrace(None)
tsk.tempval = new_f
stackless.tasklet.setup(tsk, f, args, kwargs)
return tsk
def settrace(tsk, tb):
if hasattr(tsk.frame, "f_trace"):
tsk.frame.f_trace = tb
sys.settrace(tb)
self.__oldstacklesscall__ = stackless.tasklet.__call__
stackless.tasklet.settrace = settrace
stackless.tasklet.__call__ = __call__
if sys.platform == 'cli':
self.frames = []
if sys.platform == 'cli':
# workaround an IronPython bug where we're sometimes missing the back frames
# http://ironpython.codeplex.com/workitem/31437
def push_frame(self, frame):
self.cur_frame = frame
self.frames.append(frame)
def pop_frame(self):
self.frames.pop()
self.cur_frame = self.frames[-1]
else:
def push_frame(self, frame):
self.cur_frame = frame
def pop_frame(self):
self.cur_frame = self.cur_frame.f_back
def context_dispatcher(self, old, new):
self.stepping = STEPPING_NONE
# for those tasklets that started before we started tracing
# we need to make sure that the trace is set by patching
# it in the context switch
if old and new:
if hasattr(new.frame, "f_trace") and not new.frame.f_trace:
sys.call_tracing(new.settrace,(self.trace_func,))
def _stackless_schedule_cb(self, prev, next):
current = stackless.getcurrent()
if not current:
return
current_tf = current.trace_function
try:
current.trace_function = None
self.stepping = STEPPING_NONE
# If the current frame has no trace function, we may need to get it
# from the previous frame, depending on how we ended up in the
# callback.
if current_tf is None:
f_back = current.frame.f_back
if f_back is not None:
current_tf = f_back.f_trace
if next is not None:
# Assign our trace function to the current stack
f = next.frame
if next is current:
f = f.f_back
while f:
if isinstance(f, types.FrameType):
f.f_trace = self.trace_func
f = f.f_back
next.trace_function = self.trace_func
finally:
current.trace_function = current_tf
def trace_func(self, frame, event, arg):
# If we're so far into process shutdown that sys is already gone, just stop tracing.
if sys is None:
return None
elif self.is_sending:
# https://pytools.codeplex.com/workitem/1864
# we're currently doing I/O w/ the socket, we don't want to deliver
# any breakpoints or async breaks because we'll deadlock. Continue
# to return the trace function so all of our frames remain
# balanced. A better way to deal with this might be to do
# sys.settrace(None) when we take the send lock, but that's much
# more difficult because our send context manager is used both
# inside and outside of the trace function, and so is used when
# tracing is enabled and disabled, and so it's very easy to get our
# current frame tracking to be thrown off...
return self.trace_func
try:
# if should_debug_code(frame.f_code) is not true during attach
# the current frame is None and a pop_frame will cause an exception and
# break the debugger
if self.cur_frame is None:
# happens during attach, we need frame for blocking
self.push_frame(frame)
if self.stepping == STEPPING_BREAK and should_debug_code(frame.f_code):
if self.detach:
if stackless is not None:
stackless.set_schedule_callback(None)
stackless.tasklet.__call__ = self.__oldstacklesscall__
sys.settrace(None)
return None
self.async_break()
return self._events[event](frame, arg)
except (StackOverflowException, KeyboardInterrupt):
# stack overflow, disable tracing
return self.trace_func
def handle_call(self, frame, arg):
self.push_frame(frame)
if DJANGO_BREAKPOINTS:
source_obj = get_django_frame_source(frame)
if source_obj is not None:
origin, (start, end), lineNumber = source_obj
active_bps = DJANGO_BREAKPOINTS.get(origin.lower())
should_break = False
if active_bps is not None and origin != '<unknown source>':
should_break, bkpt_id = active_bps.should_break(start, end)
isPlainText = active_bps.is_range_plain_text(start, end)
if isPlainText:
should_break = False
if should_break:
probe_stack()
update_all_thread_stacks(self)
self.block(lambda: (report_breakpoint_hit(bkpt_id, self.id), mark_all_threads_for_break(skip_thread = self)))
if not should_break and self.django_stepping:
self.django_stepping = None
self.stepping = STEPPING_OVER
self.block_maybe_attach()
if frame.f_code.co_name == '<module>' and frame.f_code.co_filename not in ['<string>', '<stdin>']:
probe_stack()
code, module = new_module(frame)
if not DETACHED: