1- # ############################################################################
2- #
3- # Copyright (c) Microsoft Corporation.
4- #
5- # This source code is subject to terms and conditions of the Apache License, Version 2.0. A
6- # copy of the license can be found in the License.html file at the root of this distribution. If
7- # you cannot locate the Apache License, Version 2.0, please send an email to
8- # vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
9- # by the terms of the Apache License, Version 2.0.
10- #
11- # You must not remove this notice, or any other, from this software.
12- #
13- # ###########################################################################
1+ # Python Tools for Visual Studio
2+ # Copyright(c) Microsoft Corporation
3+ # All rights reserved.
4+ #
5+ # Licensed under the Apache License, Version 2.0 (the License); you may not use
6+ # this file except in compliance with the License. You may obtain a copy of the
7+ # License at http://www.apache.org/licenses/LICENSE-2.0
8+ #
9+ # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
10+ # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
11+ # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
12+ # MERCHANTABLITY OR NON-INFRINGEMENT.
13+ #
14+ # See the Apache Version 2.0 License for specific language governing
15+ # permissions and limitations under the License.
1416
1517from __future__ import with_statement
1618
19+ __author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
20+ __version__ = "3.0.0.0"
21+
1722# This module MUST NOT import threading in global scope. This is because in a direct (non-ptvsd)
1823# attach scenario, it is loaded on the injected debugger attach thread, and if threading module
1924# hasn't been loaded already, it will assume that the thread on which it is being loaded is the
@@ -105,7 +110,21 @@ def thread_creator(func, args, kwargs = {}, *extra_args):
105110
106111 return _start_new_thread (new_thread_wrapper , (func , args , kwargs ))
107112
108- _start_new_thread = thread .start_new_thread
113+ _thread_start_new_thread = thread .start_new_thread
114+ def _start_new_thread (func , args , kwargs = {}):
115+ t_lock = thread .allocate_lock ()
116+ t_lock .acquire ()
117+
118+ tid = []
119+ def thread_starter (a , kw ):
120+ tid .append (thread .get_ident ())
121+ t_lock .release ()
122+ return func (* a , ** kw )
123+
124+ _thread_start_new_thread (thread_starter , (args , kwargs ))
125+ with t_lock :
126+ return tid [0 ]
127+
109128THREADS = {}
110129THREADS_LOCK = thread .allocate_lock ()
111130MODULES = []
@@ -114,6 +133,8 @@ def thread_creator(func, args, kwargs = {}, *extra_args):
114133DEBUG_STDLIB = False
115134DJANGO_DEBUG = False
116135
136+ RICH_EXCEPTIONS = False
137+
117138# Py3k compat - alias unicode to str
118139try :
119140 unicode
@@ -330,6 +351,7 @@ class StackOverflowException(Exception): pass
330351EXTT = to_bytes ('EXTT' )
331352EXIT = to_bytes ('EXIT' )
332353EXCP = to_bytes ('EXCP' )
354+ EXC2 = to_bytes ('EXC2' )
333355MODL = to_bytes ('MODL' )
334356STPD = to_bytes ('STPD' )
335357BRKS = to_bytes ('BRKS' )
@@ -366,12 +388,14 @@ def is_file_in_zip(filename):
366388 return False
367389 elif parent in KNOWN_ZIPS :
368390 return True
369- elif path .isdir (parent ):
391+ elif path .isfile (parent ):
392+ KNOWN_ZIPS .add (parent )
393+ return True
394+ elif path .isdir (parent ):
370395 KNOWN_DIRECTORIES .add (parent )
371396 return False
372397 else :
373- KNOWN_ZIPS .add (parent )
374- return True
398+ return is_file_in_zip (parent )
375399
376400def lookup_builtin (name , frame ):
377401 try :
@@ -455,8 +479,7 @@ def should_break(self, thread, ex_type, ex_value, trace):
455479 if break_type :
456480 if issubclass (ex_type , SystemExit ):
457481 if not BREAK_ON_SYSTEMEXIT_ZERO :
458- if ((isinstance (ex_value , int ) and not ex_value ) or
459- (isinstance (ex_value , SystemExit ) and not ex_value .code )):
482+ if not ex_value or (isinstance (ex_value , SystemExit ) and not ex_value .code ):
460483 break_type = BREAK_TYPE_NONE
461484
462485 return break_type
@@ -560,7 +583,7 @@ def should_debug_code(code):
560583
561584 return True
562585
563- attach_lock = thread .allocate ()
586+ attach_lock = thread .allocate_lock ()
564587attach_sent_break = False
565588
566589local_path_to_vs_path = {}
@@ -642,9 +665,12 @@ def line_locations(self):
642665 line_info = []
643666 file_len = 0
644667 for line in contents :
668+ line_len = len (line )
645669 if not line_info and line .startswith (BOM_UTF8 ):
646- line = line [3 :] # Strip the BOM, Django seems to ignore this...
647- file_len += len (line )
670+ line_len -= len (BOM_UTF8 ) # Strip the BOM, Django seems to ignore this...
671+ if line .endswith (to_bytes ('\r \n ' )):
672+ line_len -= 1 # Django normalizes newlines to \n
673+ file_len += line_len
648674 line_info .append (file_len )
649675 contents .close ()
650676 self ._line_locations = line_info
@@ -675,10 +701,26 @@ def should_break(self, start, end):
675701def get_django_frame_source (frame ):
676702 if frame .f_code .co_name == 'render' :
677703 self_obj = frame .f_locals .get ('self' , None )
678- if self_obj is not None and type (self_obj ).__name__ != 'TextNode' :
679- source_obj = getattr (self_obj , 'source' , None )
680- if source_obj is not None :
681- return source_obj
704+ if self_obj is None :
705+ return None
706+ name = type (self_obj ).__name__
707+ if name in ('Template' , 'TextNode' ):
708+ return None
709+ source_obj = getattr (self_obj , 'source' , None )
710+ if source_obj and hasattr (source_obj , '__len__' ) and len (source_obj ) == 2 :
711+ return str (source_obj [0 ]), source_obj [1 ]
712+
713+ token_obj = getattr (self_obj , 'token' , None )
714+ if token_obj is None :
715+ return None
716+ template_obj = getattr (frame .f_locals .get ('context' , None ), 'template' , None )
717+ if template_obj is None :
718+ return None
719+ template_name = getattr (template_obj , 'origin' , None )
720+ position = getattr (token_obj , 'position' , None )
721+ if template_name and position :
722+ return str (template_name ), position
723+
682724
683725 return None
684726
@@ -879,8 +921,8 @@ def handle_call(self, frame, arg):
879921 source_obj = get_django_frame_source (frame )
880922 if source_obj is not None :
881923 origin , (start , end ) = source_obj
882-
883- active_bps = DJANGO_BREAKPOINTS .get (origin .name . lower ())
924+
925+ active_bps = DJANGO_BREAKPOINTS .get (origin .lower ())
884926 should_break = False
885927 if active_bps is not None :
886928 should_break , bkpt_id = active_bps .should_break (start , end )
@@ -1436,7 +1478,13 @@ def get_frame_list(self):
14361478 # collect globals used locally, skipping undefined found in builtins
14371479 f_globals = cur_frame .f_globals
14381480 if f_globals : # ensure globals to work with (IPy may have None for cur_frame.f_globals for frames within stdlib)
1439- self .collect_variables (vars , f_globals , cur_frame .f_code .co_names , treated , skip_unknown = True )
1481+ self .collect_variables (
1482+ vars ,
1483+ f_globals ,
1484+ getattr (cur_frame .f_code , 'co_names' , ()),
1485+ treated ,
1486+ skip_unknown = True
1487+ )
14401488
14411489 frame_info = None
14421490
@@ -1582,10 +1630,11 @@ class DebuggerLoop(object):
15821630
15831631 instance = None
15841632
1585- def __init__ (self , conn ):
1633+ def __init__ (self , conn , rich_exceptions = False ):
15861634 DebuggerLoop .instance = self
15871635 self .conn = conn
15881636 self .repl_backend = None
1637+ self .rich_exceptions = rich_exceptions
15891638 self .command_table = {
15901639 to_bytes ('stpi' ) : self .command_step_into ,
15911640 to_bytes ('stpo' ) : self .command_step_out ,
@@ -1599,6 +1648,7 @@ def __init__(self, conn):
15991648 to_bytes ('brka' ) : self .command_break_all ,
16001649 to_bytes ('resa' ) : self .command_resume_all ,
16011650 to_bytes ('rest' ) : self .command_resume_thread ,
1651+ to_bytes ('thrf' ) : self .command_get_thread_frames ,
16021652 to_bytes ('ares' ) : self .command_auto_resume ,
16031653 to_bytes ('exec' ) : self .command_execute_code ,
16041654 to_bytes ('chld' ) : self .command_enum_children ,
@@ -1782,6 +1832,13 @@ def command_break_all(self):
17821832 SEND_BREAK_COMPLETE = True
17831833 mark_all_threads_for_break ()
17841834
1835+ def command_get_thread_frames (self ):
1836+ tid = read_int (self .conn )
1837+ THREADS_LOCK .acquire ()
1838+ thread = THREADS [tid ]
1839+ THREADS_LOCK .release ()
1840+ thread .enum_thread_frames_locally ()
1841+
17851842 def command_resume_all (self ):
17861843 # resume all
17871844 THREADS_LOCK .acquire ()
@@ -1983,7 +2040,6 @@ def report_thread_exit(old_thread):
19832040
19842041def report_exception (frame , exc_info , tid , break_type ):
19852042 exc_type = exc_info [0 ]
1986- exc_name = get_exception_name (exc_type )
19872043 exc_value = exc_info [1 ]
19882044 tb_value = exc_info [2 ]
19892045
@@ -1992,14 +2048,45 @@ def report_exception(frame, exc_info, tid, break_type):
19922048 # so we can get the correct msg.
19932049 exc_value = exc_type (* exc_value )
19942050
1995- excp_text = str (exc_value )
2051+ data = {
2052+ 'typename' : get_exception_name (exc_type ),
2053+ 'message' : str (exc_value ),
2054+ }
2055+ if break_type == 1 :
2056+ data ['breaktype' ] = 'unhandled'
2057+ if tb_value :
2058+ try :
2059+ data ['trace' ] = '\n ' .join (',' .join (repr (v ) for v in line ) for line in traceback .extract_tb (tb_value ))
2060+ except :
2061+ pass
2062+ if not DJANGO_DEBUG or get_django_frame_source (frame ) is None :
2063+ data ['excvalue' ] = '__exception_info'
2064+ i = 0
2065+ while data ['excvalue' ] in frame .f_locals :
2066+ i += 1
2067+ data ['excvalue' ] = '__exception_info_%d' % i
2068+ frame .f_locals [data ['excvalue' ]] = {
2069+ 'exception' : exc_value ,
2070+ 'exception_type' : exc_type ,
2071+ 'message' : data ['message' ],
2072+ }
19962073
19972074 with _SendLockCtx :
1998- write_bytes (conn , EXCP )
1999- write_string (conn , exc_name )
2000- write_int (conn , tid )
2001- write_int (conn , break_type )
2002- write_string (conn , excp_text )
2075+ if RICH_EXCEPTIONS :
2076+ write_bytes (conn , EXC2 )
2077+ write_int (conn , tid )
2078+ write_int (conn , len (data ))
2079+ for key , value in data .items ():
2080+ write_string (conn , key )
2081+ write_string (conn , str (value ))
2082+ else :
2083+ # Old message is fixed format. If RichExceptions is not passed in
2084+ # debug options, we'll send this format.
2085+ write_bytes (conn , EXCP )
2086+ write_string (conn , str (data ['typename' ]))
2087+ write_int (conn , tid )
2088+ write_int (conn , 1 if 'breaktype' in data else 0 )
2089+ write_string (conn , str (data ['message' ]))
20032090
20042091def new_module (frame ):
20052092 mod = Module (get_code_filename (frame .f_code ))
@@ -2172,6 +2259,7 @@ def attach_process(port_num, debug_id, debug_options, report = False, block = Fa
21722259
21732260def attach_process_from_socket (sock , debug_options , report = False , block = False ):
21742261 global conn , attach_sent_break , DETACHED , DEBUG_STDLIB , BREAK_ON_SYSTEMEXIT_ZERO , DJANGO_DEBUG
2262+ global RICH_EXCEPTIONS
21752263
21762264 BREAK_ON_SYSTEMEXIT_ZERO = 'BreakOnSystemExitZero' in debug_options
21772265 DJANGO_DEBUG = 'DjangoDebugging' in debug_options
@@ -2186,10 +2274,12 @@ def attach_process_from_socket(sock, debug_options, report = False, block = Fals
21862274 wait_on_normal_exit = 'WaitOnNormalExit' in debug_options
21872275 wait_on_abnormal_exit = 'WaitOnAbnormalExit' in debug_options
21882276
2277+ RICH_EXCEPTIONS = 'RichExceptions' in debug_options
2278+
21892279 def _excepthook (exc_type , exc_value , exc_tb ):
21902280 # Display the exception and wait on exit
21912281 if exc_type is SystemExit :
2192- if (wait_on_abnormal_exit and exc_value .code != 0 ) or (wait_on_normal_exit and exc_value .code == 0 ):
2282+ if (wait_on_abnormal_exit and exc_value .code ) or (wait_on_normal_exit and not exc_value .code ):
21932283 print_exception (exc_type , exc_value , exc_tb )
21942284 do_wait ()
21952285 else :
@@ -2494,4 +2584,4 @@ def debug(file, port_num, debug_id, debug_options, run_as = 'script'):
24942584 get_code (exec_module ),
24952585 get_code (exec_code ),
24962586 get_code (new_thread_wrapper )
2497- ))
2587+ ))
0 commit comments