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. 16 17from __future__ import with_statement 18 19__author__ = "Microsoft Corporation <ptvshelp@microsoft.com>" 20__version__ = "3.1.0.0" 21 22# This module MUST NOT import threading in global scope. This is because in a direct (non-ptvsd) 23# attach scenario, it is loaded on the injected debugger attach thread, and if threading module 24# hasn't been loaded already, it will assume that the thread on which it is being loaded is the 25# main thread. This will cause issues when the thread goes away after attach completes. 26 27try: 28 import thread 29except ImportError: 30 # Renamed in Python3k 31 import _thread as thread 32try: 33 from ssl import SSLError 34except: 35 SSLError = None 36 37import sys 38import socket 39import select 40import time 41import struct 42import imp 43import traceback 44import random 45import os 46import inspect 47import types 48from collections import deque 49 50try: 51 # In the local attach scenario, visualstudio_py_util is injected into globals() 52 # by PyDebugAttach before loading this module, and cannot be imported. 53 _vspu = visualstudio_py_util 54except: 55 try: 56 import visualstudio_py_util as _vspu 57 except ImportError: 58 import ptvsd.visualstudio_py_util as _vspu 59to_bytes = _vspu.to_bytes 60read_bytes = _vspu.read_bytes 61read_int = _vspu.read_int 62read_string = _vspu.read_string 63write_bytes = _vspu.write_bytes 64write_int = _vspu.write_int 65write_string = _vspu.write_string 66 67try: 68 unicode 69except NameError: 70 unicode = str 71 72try: 73 BaseException 74except NameError: 75 # BaseException not defined until Python 2.5 76 BaseException = Exception 77 78DEBUG = os.environ.get('DEBUG_REPL') is not None 79 80__all__ = ['ReplBackend', 'BasicReplBackend', 'BACKEND'] 81 82def _debug_write(out): 83 if DEBUG: 84 sys.__stdout__.write(out) 85 sys.__stdout__.flush() 86 87 88class SafeSendLock(object): 89 """a lock which ensures we're released if we take a KeyboardInterrupt exception acquiring it""" 90 def __init__(self): 91 self.lock = thread.allocate_lock() 92 93 def __enter__(self): 94 self.acquire() 95 96 def __exit__(self, exc_type, exc_value, tb): 97 self.release() 98 99 def acquire(self): 100 try: 101 self.lock.acquire() 102 except KeyboardInterrupt: 103 try: 104 self.lock.release() 105 except: 106 pass 107 raise 108 109 def release(self): 110 self.lock.release() 111 112def _command_line_to_args_list(cmdline): 113 """splits a string into a list using Windows command line syntax.""" 114 args_list = [] 115 116 if cmdline and cmdline.strip(): 117 from ctypes import c_int, c_voidp, c_wchar_p 118 from ctypes import byref, POINTER, WinDLL 119 120 clta = WinDLL('shell32').CommandLineToArgvW 121 clta.argtypes = [c_wchar_p, POINTER(c_int)] 122 clta.restype = POINTER(c_wchar_p) 123 124 lf = WinDLL('kernel32').LocalFree 125 lf.argtypes = [c_voidp] 126 127 pNumArgs = c_int() 128 r = clta(cmdline, byref(pNumArgs)) 129 if r: 130 for index in range(0, pNumArgs.value): 131 if sys.hexversion >= 0x030000F0: 132 argval = r[index] 133 else: 134 argval = r[index].encode('ascii', 'replace') 135 args_list.append(argval) 136 lf(r) 137 else: 138 sys.stderr.write('Error parsing script arguments:\n') 139 sys.stderr.write(cmdline + '\n') 140 141 return args_list 142 143 144class UnsupportedReplException(Exception): 145 def __init__(self, reason): 146 self.reason = reason 147 148# save the start_new_thread so we won't debug/break into the REPL comm thread. 149start_new_thread = thread.start_new_thread 150class ReplBackend(object): 151 """back end for executing REPL code. This base class handles all of the 152communication with the remote process while derived classes implement the 153actual inspection and introspection.""" 154 _MRES = to_bytes('MRES') 155 _SRES = to_bytes('SRES') 156 _MODS = to_bytes('MODS') 157 _IMGD = to_bytes('IMGD') 158 _PRPC = to_bytes('PRPC') 159 _RDLN = to_bytes('RDLN') 160 _STDO = to_bytes('STDO') 161 _STDE = to_bytes('STDE') 162 _DBGA = to_bytes('DBGA') 163 _DETC = to_bytes('DETC') 164 _DPNG = to_bytes('DPNG') 165 _DXAM = to_bytes('DXAM') 166 _CHWD = to_bytes('CHWD') 167 168 _MERR = to_bytes('MERR') 169 _SERR = to_bytes('SERR') 170 _ERRE = to_bytes('ERRE') 171 _EXIT = to_bytes('EXIT') 172 _DONE = to_bytes('DONE') 173 _MODC = to_bytes('MODC') 174 175 def __init__(self, *args, **kwargs): 176 import threading 177 self.conn = None 178 self.send_lock = SafeSendLock() 179 self.input_event = threading.Lock() 180 self.input_event.acquire() # lock starts acquired (we use it like a manual reset event) 181 self.input_string = None 182 self.exit_requested = False 183 184 def connect(self, port): 185 self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 186 self.conn.connect(('127.0.0.1', port)) 187 188 # start a new thread for communicating w/ the remote process 189 start_new_thread(self._repl_loop, ()) 190 191 def connect_using_socket(self, socket): 192 self.conn = socket 193 start_new_thread(self._repl_loop, ()) 194 195 def _repl_loop(self): 196 """loop on created thread which processes communicates with the REPL window""" 197 try: 198 while True: 199 if self.check_for_exit_repl_loop(): 200 break 201 202 # we receive a series of 4 byte commands. Each command then 203 # has it's own format which we must parse before continuing to 204 # the next command. 205 self.flush() 206 self.conn.settimeout(10) 207 208 # 2.x raises SSLError in case of timeout (http://bugs.python.org/issue10272) 209 if SSLError: 210 timeout_exc_types = (socket.timeout, SSLError) 211 else: 212 timeout_exc_types = socket.timeout 213 try: 214 inp = read_bytes(self.conn, 4) 215 except timeout_exc_types: 216 r, w, x = select.select([], [], [self.conn], 0) 217 if x: 218 # an exception event has occured on the socket... 219 raise 220 continue 221 222 self.conn.settimeout(None) 223 if inp == '': 224 break 225 self.flush() 226 227 cmd = ReplBackend._COMMANDS.get(inp) 228 if cmd is not None: 229 cmd(self) 230 except: 231 _debug_write('error in repl loop') 232 _debug_write(traceback.format_exc()) 233 self.exit_process() 234 235 time.sleep(2) # try and exit gracefully, then interrupt main if necessary 236 237 if sys.platform == 'cli': 238 # just kill us as fast as possible 239 import System 240 System.Environment.Exit(1) 241 242 self.interrupt_main() 243 244 def check_for_exit_repl_loop(self): 245 return False 246 247 def _cmd_run(self): 248 """runs the received snippet of code""" 249 self.run_command(read_string(self.conn)) 250 251 def _cmd_abrt(self): 252 """aborts the current running command""" 253 # abort command, interrupts execution of the main thread. 254 self.interrupt_main() 255 256 def _cmd_exit(self): 257 """exits the interactive process""" 258 self.exit_requested = True 259 self.exit_process() 260 261 def _cmd_mems(self): 262 """gets the list of members available for the given expression""" 263 expression = read_string(self.conn) 264 try: 265 name, inst_members, type_members = self.get_members(expression) 266 except: 267 with self.send_lock: 268 write_bytes(self.conn, ReplBackend._MERR) 269 _debug_write('error in eval') 270 _debug_write(traceback.format_exc()) 271 else: 272 with self.send_lock: 273 write_bytes(self.conn, ReplBackend._MRES) 274 write_string(self.conn, name) 275 self._write_member_dict(inst_members) 276 self._write_member_dict(type_members) 277 278 def _cmd_sigs(self): 279 """gets the signatures for the given expression""" 280 expression = read_string(self.conn) 281 try: 282 sigs = self.get_signatures(expression) 283 except: 284 with self.send_lock: 285 write_bytes(self.conn, ReplBackend._SERR) 286 _debug_write('error in eval') 287 _debug_write(traceback.format_exc()) 288 else: 289 with self.send_lock: 290 write_bytes(self.conn, ReplBackend._SRES) 291 # single overload 292 write_int(self.conn, len(sigs)) 293 for doc, args, vargs, varkw, defaults in sigs: 294 # write overload 295 write_string(self.conn, (doc or '')[:4096]) 296 arg_count = len(args) + (vargs is not None) + (varkw is not None) 297 write_int(self.conn, arg_count) 298 299 def_values = [''] * (len(args) - len(defaults)) + ['=' + d for d in defaults] 300 for arg, def_value in zip(args, def_values): 301 write_string(self.conn, (arg or '') + def_value) 302 if vargs is not None: 303 write_string(self.conn, '*' + vargs) 304 if varkw is not None: 305 write_string(self.conn, '**' + varkw) 306 307 def _cmd_setm(self): 308 global exec_mod 309 """sets the current module which code will execute against""" 310 mod_name = read_string(self.conn) 311 self.set_current_module(mod_name) 312 313 def _cmd_sett(self): 314 """sets the current thread and frame which code will execute against""" 315 thread_id = read_int(self.conn) 316 frame_id = read_int(self.conn) 317 frame_kind = read_int(self.conn) 318 self.set_current_thread_and_frame(thread_id, frame_id, frame_kind) 319 320 def _cmd_mods(self): 321 """gets the list of available modules""" 322 try: 323 res = self.get_module_names() 324 res.sort() 325 except: 326 res = [] 327 328 with self.send_lock: 329 write_bytes(self.conn, ReplBackend._MODS) 330 write_int(self.conn, len(res)) 331 for name, filename in res: 332 write_string(self.conn, name) 333 write_string(self.conn, filename) 334 335 def _cmd_inpl(self): 336 """handles the input command which returns a string of input""" 337 self.input_string = read_string(self.conn) 338 self.input_event.release() 339 340 def _cmd_excf(self): 341 """handles executing a single file""" 342 filename = read_string(self.conn) 343 args = read_string(self.conn) 344 self.execute_file(filename, args) 345 346 def _cmd_excx(self): 347 """handles executing a single file, module or process""" 348 filetype = read_string(self.conn) 349 filename = read_string(self.conn) 350 args = read_string(self.conn) 351 self.execute_file_ex(filetype, filename, args) 352 353 def _cmd_debug_attach(self): 354 import visualstudio_py_debugger 355 port = read_int(self.conn) 356 id = read_string(self.conn) 357 debug_options = visualstudio_py_debugger.parse_debug_options(read_string(self.conn)) 358 self.attach_process(port, id, debug_options) 359 360 _COMMANDS = { 361 to_bytes('run '): _cmd_run, 362 to_bytes('abrt'): _cmd_abrt, 363 to_bytes('exit'): _cmd_exit, 364 to_bytes('mems'): _cmd_mems, 365 to_bytes('sigs'): _cmd_sigs, 366 to_bytes('mods'): _cmd_mods, 367 to_bytes('setm'): _cmd_setm, 368 to_bytes('sett'): _cmd_sett, 369 to_bytes('inpl'): _cmd_inpl, 370 to_bytes('excf'): _cmd_excf, 371 to_bytes('excx'): _cmd_excx, 372 to_bytes('dbga'): _cmd_debug_attach, 373 } 374 375 def _write_member_dict(self, mem_dict): 376 write_int(self.conn, len(mem_dict)) 377 for name, type_name in mem_dict.items(): 378 write_string(self.conn, name) 379 write_string(self.conn, type_name) 380 381 def on_debugger_detach(self): 382 with self.send_lock: 383 write_bytes(self.conn, ReplBackend._DETC) 384 385 def init_debugger(self): 386 from os import path 387 sys.path.append(path.dirname(__file__)) 388 import visualstudio_py_debugger 389 visualstudio_py_debugger.DONT_DEBUG.append(path.normcase(__file__)) 390 new_thread = visualstudio_py_debugger.new_thread() 391 sys.settrace(new_thread.trace_func) 392 visualstudio_py_debugger.intercept_threads(True) 393 394 def send_image(self, filename): 395 with self.send_lock: 396 write_bytes(self.conn, ReplBackend._IMGD) 397 write_string(self.conn, filename) 398 399 def write_png(self, image_bytes): 400 with self.send_lock: 401 write_bytes(self.conn, ReplBackend._DPNG) 402 write_int(self.conn, len(image_bytes)) 403 write_bytes(self.conn, image_bytes) 404 405 def write_xaml(self, xaml_bytes): 406 with self.send_lock: 407 write_bytes(self.conn, ReplBackend._DXAM) 408 write_int(self.conn, len(xaml_bytes)) 409 write_bytes(self.conn, xaml_bytes) 410 411 def send_prompt(self, ps1, ps2, allow_multiple_statements): 412 """sends the current prompt to the interactive window""" 413 with self.send_lock: 414 write_bytes(self.conn, ReplBackend._PRPC) 415 write_string(self.conn, ps1) 416 write_string(self.conn, ps2) 417 write_int(self.conn, 1 if allow_multiple_statements else 0) 418 419 def send_cwd(self): 420 """sends the current working directory""" 421 with self.send_lock: 422 write_bytes(self.conn, ReplBackend._CHWD) 423 write_string(self.conn, os.getcwd()) 424 425 def send_error(self): 426 """reports that an error occured to the interactive window""" 427 with self.send_lock: 428 write_bytes(self.conn, ReplBackend._ERRE) 429 430 def send_exit(self): 431 """reports the that the REPL process has exited to the interactive window""" 432 with self.send_lock: 433 write_bytes(self.conn, ReplBackend._EXIT) 434 435 def send_command_executed(self): 436 with self.send_lock: 437 write_bytes(self.conn, ReplBackend._DONE) 438 439 def send_modules_changed(self): 440 with self.send_lock: 441 write_bytes(self.conn, ReplBackend._MODC) 442 443 def read_line(self): 444 """reads a line of input from standard input""" 445 with self.send_lock: 446 write_bytes(self.conn, ReplBackend._RDLN) 447 self.input_event.acquire() 448 return self.input_string 449 450 def write_stdout(self, value): 451 """writes a string to standard output in the remote console""" 452 with self.send_lock: 453 write_bytes(self.conn, ReplBackend._STDO) 454 write_string(self.conn, value) 455 456 def write_stderr(self, value): 457 """writes a string to standard input in the remote console""" 458 with self.send_lock: 459 write_bytes(self.conn, ReplBackend._STDE) 460 write_string(self.conn, value) 461 462 ################################################################ 463 # Implementation of execution, etc... 464 465 def execution_loop(self): 466 """starts processing execution requests""" 467 raise NotImplementedError 468 469 def run_command(self, command): 470 """runs the specified command which is a string containing code""" 471 raise NotImplementedError 472 473 def execute_file(self, filename, args): 474 """executes the given filename as the main module""" 475 return self.execute_file_ex('script', filename, args) 476 477 def execute_file_ex(self, filetype, filename, args): 478 """executes the given filename as a 'script', 'module' or 'process'.""" 479 raise NotImplementedError 480 481 def interrupt_main(self): 482 """aborts the current running command""" 483 raise NotImplementedError 484 485 def exit_process(self): 486 """exits the REPL process""" 487 raise NotImplementedError 488 489 def get_members(self, expression): 490 """returns a tuple of the type name, instance members, and type members""" 491 raise NotImplementedError 492 493 def get_signatures(self, expression): 494 """returns doc, args, vargs, varkw, defaults.""" 495 raise NotImplementedError 496 497 def set_current_module(self, module): 498 """sets the module which code executes against""" 499 raise NotImplementedError 500 501 def set_current_thread_and_frame(self, thread_id, frame_id, frame_kind): 502 """sets the current thread and frame which code will execute against""" 503 raise NotImplementedError 504 505 def get_module_names(self): 506 """returns a list of module names""" 507 raise NotImplementedError 508 509 def flush(self): 510 """flushes the stdout/stderr buffers""" 511 raise NotImplementedError 512 513 def attach_process(self, port, debugger_id, debug_options): 514 """starts processing execution requests""" 515 raise NotImplementedError 516 517def exit_work_item(): 518 sys.exit(0) 519 520 521if sys.platform == 'cli': 522 # We need special handling to reset the abort for keyboard interrupt exceptions 523 class ReplAbortException(Exception): pass 524 525 import clr 526 clr.AddReference('Microsoft.Dynamic') 527 clr.AddReference('Microsoft.Scripting') 528 clr.AddReference('IronPython') 529 from Microsoft.Scripting import KeyboardInterruptException 530 from Microsoft.Scripting import ParamDictionaryAttribute 531 from IronPython.Runtime.Operations import PythonOps 532 from IronPython.Runtime import PythonContext 533 from Microsoft.Scripting import SourceUnit, SourceCodeKind 534 from Microsoft.Scripting.Runtime import Scope 535 536 python_context = clr.GetCurrentRuntime().GetLanguage(PythonContext) 537 538 from System import DBNull, ParamArrayAttribute 539 builtin_method_descriptor_type = type(list.append) 540 541 import System 542 NamespaceType = type(System) 543 544class _OldClass: 545 pass 546 547_OldClassType = type(_OldClass) 548_OldInstanceType = type(_OldClass()) 549 550class BasicReplBackend(ReplBackend): 551 future_bits = 0x3e010 # code flags used to mark future bits 552 553 """Basic back end which executes all Python code in-proc""" 554 def __init__(self, mod_name='__main__'): 555 import threading 556 ReplBackend.__init__(self) 557 if mod_name is not None: 558 if sys.platform == 'cli': 559 self.exec_mod = Scope() 560 self.exec_mod.__name__ = '__main__' 561 else: 562 sys.modules[mod_name] = self.exec_mod = imp.new_module(mod_name) 563 else: 564 self.exec_mod = sys.modules['__main__'] 565 566 self.code_flags = 0 567 self.execute_item = None 568 self.execute_item_lock = threading.Lock() 569 self.execute_item_lock.acquire() # lock starts acquired (we use it like manual reset event) 570 571 def init_connection(self): 572 sys.stdout = _ReplOutput(self, is_stdout = True) 573 sys.stderr = _ReplOutput(self, is_stdout = False) 574 sys.stdin = _ReplInput(self) 575 if sys.platform == 'cli': 576 import System 577 System.Console.SetOut(DotNetOutput(self, True)) 578 System.Console.SetError(DotNetOutput(self, False)) 579 580 def connect(self, port): 581 ReplBackend.connect(self, port) 582 self.init_connection() 583 584 def connect_using_socket(self, socket): 585 ReplBackend.connect_using_socket(self, socket) 586 self.init_connection() 587 588 def run_file_as_main(self, filename, args): 589 f = open(filename, 'rb') 590 try: 591 contents = f.read().replace(to_bytes('\r\n'), to_bytes('\n')) 592 finally: 593 f.close() 594 sys.argv = [filename] 595 sys.argv.extend(_command_line_to_args_list(args)) 596 self.exec_mod.__file__ = filename 597 if sys.platform == 'cli': 598 code = python_context.CreateSnippet(contents, None, SourceCodeKind.File) 599 code.Execute(self.exec_mod) 600 else: 601 self.code_flags = 0 602 real_file = filename 603 if isinstance(filename, unicode) and unicode is not str: 604 # http://pytools.codeplex.com/workitem/696 605 # We need to encode the unicode filename here, Python 2.x will throw trying 606 # to convert it to ASCII instead of the filesystem encoding. 607 real_file = filename.encode(sys.getfilesystemencoding()) 608 code = compile(contents, real_file, 'exec') 609 self.code_flags |= (code.co_flags & BasicReplBackend.future_bits) 610 exec(code, self.exec_mod.__dict__, self.exec_mod.__dict__) 611 612 def python_executor(self, code): 613 """we can't close over unbound variables in execute_code_work_item 614due to the exec, so we do it here""" 615 def func(): 616 code.Execute(self.exec_mod) 617 return func 618 619 def execute_code_work_item(self): 620 _debug_write('Executing: ' + repr(self.current_code)) 621 stripped_code = self.current_code.strip() 622 if stripped_code: 623 if sys.platform == 'cli': 624 code_to_send = '' 625 for line in stripped_code.split('\n'): 626 stripped = line.strip() 627 if (stripped.startswith('#') or not stripped) and not code_to_send: 628 continue 629 code_to_send += line + '\n' 630 631 code = python_context.CreateSnippet(code_to_send, None, SourceCodeKind.InteractiveCode) 632 dispatcher = clr.GetCurrentRuntime().GetLanguage(PythonContext).GetCommandDispatcher() 633 if dispatcher is not None: 634 dispatcher(self.python_executor(code)) 635 else: 636 code.Execute(self.exec_mod) 637 else: 638 code = compile(self.current_code, '<stdin>', 'single', self.code_flags) 639 self.code_flags |= (code.co_flags & BasicReplBackend.future_bits) 640 exec(code, self.exec_mod.__dict__, self.exec_mod.__dict__) 641 self.current_code = None 642 643 def run_one_command(self, cur_modules, cur_ps1, cur_ps2): 644 # runs a single iteration of an input, execute file, etc... 645 # This is extracted into it's own method so we play nice w/ IronPython thread abort. 646 # Otherwise we have a nested exception hanging around and the 2nd abort doesn't 647 # work (that's probably an IronPython bug) 648 try: 649 new_modules = self._get_cur_module_set() 650 try: 651 if new_modules != cur_modules: 652 self.send_modules_changed() 653 except: 654 pass 655 cur_modules = new_modules 656 657 self.execute_item_lock.acquire() 658 cur_cwd = os.getcwd() 659 660 if self.check_for_exit_execution_loop(): 661 return True, None, None, None 662 663 if self.execute_item is not None: 664 try: 665 self.execute_item() 666 finally: 667 self.execute_item = None 668 669 try: 670 self.send_command_executed() 671 except SocketError: 672 return True, None, None, None 673 674 try: 675 if cur_ps1 != sys.ps1 or cur_ps2 != sys.ps2: 676 new_ps1 = str(sys.ps1) 677 new_ps2 = str(sys.ps2) 678 679 self.send_prompt(new_ps1, new_ps2, allow_multiple_statements=False) 680 681 cur_ps1 = new_ps1 682 cur_ps2 = new_ps2 683 except Exception: 684 pass 685 try: 686 if cur_cwd != os.getcwd(): 687 self.send_cwd() 688 except Exception: 689 pass 690 except SystemExit: 691 self.send_error() 692 self.send_exit() 693 # wait for ReplEvaluator to send back exit requested which will indicate 694 # that all the output has been processed. 695 while not self.exit_requested: 696 time.sleep(.25) 697 return True, None, None, None 698 except BaseException: 699 _debug_write('Exception') 700 exc_type, exc_value, exc_tb = sys.exc_info() 701 if sys.platform == 'cli': 702 if isinstance(exc_value.clsException, System.Threading.ThreadAbortException): 703 try: 704 System.Threading.Thread.ResetAbort() 705 except SystemError: 706 pass 707 sys.stderr.write('KeyboardInterrupt') 708 else: 709 # let IronPython format the exception so users can do -X:ExceptionDetail or -X:ShowClrExceptions 710 exc_next = self.skip_internal_frames(exc_tb) 711 sys.stderr.write(''.join(traceback.format_exception(exc_type, exc_value, exc_next))) 712 else: 713 exc_next = self.skip_internal_frames(exc_tb) 714 sys.stderr.write(''.join(traceback.format_exception(exc_type, exc_value, exc_next))) 715 716 try: 717 self.send_error() 718 except SocketError: 719 _debug_write('err sending DONE') 720 return True, None, None, None 721 722 return False, cur_modules, cur_ps1, cur_ps2 723 724 def skip_internal_frames(self, tb): 725 """return the first frame outside of the repl/debugger code""" 726 while tb is not None and self.is_internal_frame(tb): 727 tb = tb.tb_next 728 return tb 729 730 def is_internal_frame(self, tb): 731 """return true if the frame is from internal code (repl or debugger)""" 732 f = tb.tb_frame 733 co = f.f_code 734 filename = co.co_filename 735 return filename.endswith('visualstudio_py_repl.py') or filename.endswith('visualstudio_py_debugger.py') 736 737 def execution_loop(self): 738 """loop on the main thread which is responsible for executing code""" 739 740 if sys.platform == 'cli' and sys.version_info[:3] < (2, 7, 1): 741 # IronPython doesn't support thread.interrupt_main until 2.7.1 742 import System 743 self.main_thread = System.Threading.Thread.CurrentThread 744 745 # save ourselves so global lookups continue to work (required pre-2.6)... 746 cur_modules = set() 747 try: 748 cur_ps1 = sys.ps1 749 cur_ps2 = sys.ps2 750 except: 751 # CPython/IronPython don't set sys.ps1 for non-interactive sessions, Jython and PyPy do 752 sys.ps1 = cur_ps1 = '>>> ' 753 sys.ps2 = cur_ps2 = '... ' 754 755 self.send_prompt(cur_ps1, cur_ps2, allow_multiple_statements=False) 756 757 while True: 758 exit, cur_modules, cur_ps1, cur_ps2 = self.run_one_command(cur_modules, cur_ps1, cur_ps2) 759 if exit: 760 return 761 762 def check_for_exit_execution_loop(self): 763 return False 764 765 def execute_script_work_item(self): 766 self.run_file_as_main(self.current_code, self.current_args) 767 768 def execute_module_work_item(self): 769 new_argv = [''] + _command_line_to_args_list(self.current_args) 770 old_argv = sys.argv 771 import runpy 772 try: 773 sys.argv = new_argv 774 runpy.run_module(self.current_code, alter_sys=True) 775 except Exception: 776 traceback.print_exc() 777 finally: 778 sys.argv = old_argv 779 780 def execute_process_work_item(self): 781 try: 782 from subprocess import Popen, PIPE, STDOUT 783 import codecs 784 out_codec = codecs.lookup(sys.stdout.encoding) 785 786 proc = Popen( 787 '"%s" %s' % (self.current_code, self.current_args), 788 stdout=PIPE, 789 stderr=STDOUT, 790 bufsize=0, 791 ) 792 793 for line in proc.stdout: 794 print(out_codec.decode(line, 'replace')[0].rstrip('\r\n')) 795 except Exception: 796 traceback.print_exc() 797 798 @staticmethod 799 def _get_cur_module_set(): 800 """gets the set of modules avoiding exceptions if someone puts something 801 weird in there""" 802 803 try: 804 return set(sys.modules) 805 except: 806 res = set() 807 for name in sys.modules: 808 try: 809 res.add(name) 810 except: 811 pass 812 return res 813 814 815 def run_command(self, command): 816 self.current_code = command 817 self.execute_item = self.execute_code_work_item 818 self.execute_item_lock.release() 819 820 def execute_file_ex(self, filetype, filename, args): 821 self.current_code = filename 822 self.current_args = args 823 self.execute_item = getattr(self, 'execute_%s_work_item' % filetype, None) 824 self.execute_item_lock.release() 825 826 def interrupt_main(self): 827 # acquire the send lock so we dont interrupt while we're communicting w/ the debugger 828 with self.send_lock: 829 if sys.platform == 'cli' and sys.version_info[:3] < (2, 7, 1): 830 # IronPython doesn't get thread.interrupt_main until 2.7.1 831 self.main_thread.Abort(ReplAbortException()) 832 else: 833 thread.interrupt_main() 834 835 def exit_process(self): 836 self.execute_item = exit_work_item 837 try: 838 self.execute_item_lock.release() 839 except: 840 pass 841 sys.exit(0) 842 843 def get_members(self, expression): 844 """returns a tuple of the type name, instance members, and type members""" 845 getattr_func = getattr 846 if not expression: 847 all_members = {} 848 if sys.platform == 'cli': 849 code = python_context.CreateSnippet('vars()', None, SourceCodeKind.AutoDetect) 850 items = code.Execute(self.exec_mod) 851 else: 852 items = self.exec_mod.__dict__ 853 854 for key, value in items.items(): 855 all_members[key] = self.get_type_name(value) 856 return '', all_members, {} 857 else: 858 if sys.platform == 'cli': 859 code = python_context.CreateSnippet(expression, None, SourceCodeKind.AutoDetect) 860 val = code.Execute(self.exec_mod) 861 862 code = python_context.CreateSnippet('dir(' + expression + ')', None, SourceCodeKind.AutoDetect) 863 members = code.Execute(self.exec_mod) 864 865 code = python_context.CreateSnippet('lambda value, name: getattr(value, name)', None, SourceCodeKind.AutoDetect) 866 getattr_func = code.Execute(self.exec_mod) 867 else: 868 val = eval(expression, self.exec_mod.__dict__, self.exec_mod.__dict__) 869 members = dir(val) 870 871 return self.collect_members(val, members, getattr_func) 872 873 def collect_members(self, val, members, getattr_func): 874 t = type(val) 875 876 inst_members = {} 877 if hasattr(val, '__dict__'): 878 # collect the instance members 879 try: 880 for mem_name in val.__dict__: 881 mem_t = self._get_member_type(val, mem_name, True, getattr_func) 882 if mem_t is not None: 883 inst_members[mem_name] = mem_t 884 except: 885 pass 886 887 # collect the type members 888 889 type_members = {} 890 for mem_name in members: 891 if mem_name not in inst_members: 892 mem_t = self._get_member_type(val, mem_name, False, getattr_func) 893 if mem_t is not None: 894 type_members[mem_name] = mem_t 895 896 897 return t.__module__ + '.' + t.__name__, inst_members, type_members 898 899 def get_ipy_sig(self, obj, ctor): 900 args = [] 901 vargs = None 902 varkw = None 903 defaults = [] 904 for param in ctor.GetParameters(): 905 if param.IsDefined(ParamArrayAttribute, False): 906 vargs = param.Name 907 elif param.IsDefined(ParamDictionaryAttribute, False): 908 varkw = param.Name 909 else: 910 args.append(param.Name) 911 912 if param.DefaultValue is not DBNull.Value: 913 defaults.append(repr(param.DefaultValue)) 914 915 return obj.__doc__, args, vargs, varkw, tuple(defaults) 916 917 def get_signatures(self, expression): 918 if sys.platform == 'cli': 919 code = python_context.CreateSnippet(expression, None, SourceCodeKind.AutoDetect) 920 val = code.Execute(self.exec_mod) 921 else: 922 val = eval(expression, self.exec_mod.__dict__, self.exec_mod.__dict__) 923 924 return self.collect_signatures(val) 925 926 def collect_signatures(self, val): 927 doc = val.__doc__ 928 type_obj = None 929 if isinstance(val, type) or isinstance(val, _OldClassType): 930 type_obj = val 931 val = val.__init__ 932 933 try: 934 args, vargs, varkw, defaults = inspect.getargspec(val) 935 except TypeError: 936 # we're not doing inspect on a Python function... 937 if sys.platform == 'cli': 938 if type_obj is not None: 939 clr_type = clr.GetClrType(type_obj) 940 ctors = clr_type.GetConstructors() 941 return [self.get_ipy_sig(type_obj, ctor) for ctor in ctors] 942 elif type(val) is types.BuiltinFunctionType: 943 return [self.get_ipy_sig(target, target.Targets[0]) for target in val.Overloads.Functions] 944 elif type(val) is builtin_method_descriptor_type: 945 val = PythonOps.GetBuiltinMethodDescriptorTemplate(val) 946 return [self.get_ipy_sig(target, target.Targets[0]) for target in val.Overloads.Functions] 947 raise 948 949 remove_self = type_obj is not None or (type(val) is types.MethodType and 950 ((sys.version_info >= (3,) and val.__self__ is not None) or 951 (sys.version_info < (3,) and val.im_self is not None))) 952 953 if remove_self: 954 # remove self for instance methods and types 955 args = args[1:] 956 957 if defaults is not None: 958 defaults = [repr(default) for default in defaults] 959 else: 960 defaults = [] 961 return [(doc, args, vargs, varkw, defaults)] 962 963 def set_current_module(self, module): 964 mod = sys.modules.get(module) 965 if mod is not None: 966 _debug_write('Setting module to ' + module) 967 if sys.platform == 'cli': 968 self.exec_mod = clr.GetClrType(type(sys)).GetProperty('Scope', System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(sys, ()) 969 else: 970 self.exec_mod = mod 971 elif module: 972 _debug_write('Unknown module ' + module) 973 974 def get_module_names(self): 975 res = [] 976 for name, module in sys.modules.items(): 977 try: 978 if name != 'visualstudio_py_repl' and name != '$visualstudio_py_debugger': 979 if sys.platform == 'cli' and type(module) is NamespaceType: 980 self.get_namespaces(name, module, res) 981 else: 982 try: 983 filename = os.path.abspath(module.__file__) 984 except Exception: 985 filename = None 986 res.append((name, filename)) 987 988 except: 989 pass 990 return res 991 992 def get_namespaces(self, basename, namespace, names): 993 names.append((basename, '')) 994 try: 995 for name in dir(namespace): 996 new_name = basename + '.' + name 997 new_namespace = getattr(namespace, name) 998 999 if type(new_namespace) is NamespaceType: 1000 self.get_namespaces(new_name, new_namespace, names) 1001 except: 1002 pass 1003 1004 def flush(self): 1005 sys.stdout.flush() 1006 1007 def do_detach(self): 1008 import visualstudio_py_debugger 1009 visualstudio_py_debugger.DETACH_CALLBACKS.remove(self.do_detach) 1010 self.on_debugger_detach() 1011 1012 def attach_process(self, port, debugger_id, debug_options): 1013 def execute_attach_process_work_item(): 1014 import visualstudio_py_debugger 1015 visualstudio_py_debugger.DETACH_CALLBACKS.append(self.do_detach) 1016 visualstudio_py_debugger.attach_process(port, debugger_id, debug_options, report=True, block=True) 1017 1018 self.execute_item = execute_attach_process_work_item 1019 self.execute_item_lock.release() 1020 1021 @staticmethod 1022 def get_type_name(val): 1023 try: 1024 mem_t = type(val) 1025 mem_t_name = mem_t.__module__ + '.' + mem_t.__name__ 1026 return mem_t_name 1027 except: 1028 pass 1029 1030 @staticmethod 1031 def _get_member_type(inst, name, from_dict, getattr_func = None): 1032 try: 1033 if from_dict: 1034 val = inst.__dict__[name] 1035 elif type(inst) is _OldInstanceType: 1036 val = getattr_func(inst.__class__, name) 1037 else: 1038 val = getattr_func(type(inst), name) 1039 mem_t_name = BasicReplBackend.get_type_name(val) 1040 return mem_t_name 1041 except: 1042 if not from_dict: 1043 try: 1044 return BasicReplBackend.get_type_name(getattr_func(inst, name)) 1045 except: 1046 pass 1047 return 1048 1049class DebugReplBackend(BasicReplBackend): 1050 def __init__(self, debugger): 1051 BasicReplBackend.__init__(self) 1052 self.debugger = debugger 1053 self.thread_id = None 1054 self.frame_id = None 1055 self.frame_kind = None 1056 self.disconnect_requested = False 1057 1058 def init_connection(self): 1059 sys.stdout = _ReplOutput(self, is_stdout = True, old_out = sys.stdout) 1060 sys.stderr = _ReplOutput(self, is_stdout = False, old_out = sys.stderr) 1061 if sys.platform == 'cli': 1062 import System 1063 self.old_cli_stdout = System.Console.Out 1064 self.old_cli_stderr = System.Console.Error 1065 System.Console.SetOut(DotNetOutput(self, True, System.Console.Out)) 1066 System.Console.SetError(DotNetOutput(self, False, System.Console.Error)) 1067 1068 def connect_from_debugger(self, port): 1069 ReplBackend.connect(self, port) 1070 self.init_connection() 1071 1072 def connect_from_debugger_using_socket(self, socket): 1073 ReplBackend.connect_using_socket(self, socket) 1074 self.init_connection() 1075 1076 def disconnect_from_debugger(self): 1077 sys.stdout = sys.stdout.old_out 1078 sys.stderr = sys.stderr.old_out 1079 if sys.platform == 'cli': 1080 System.Console.SetOut(self.old_cli_stdout) 1081 System.Console.SetError(self.old_cli_stderr) 1082 del self.old_cli_stdout 1083 del self.old_cli_stderr 1084 1085 # this tells both _repl_loop and execution_loop, each 1086 # running on its own worker thread, to exit 1087 self.disconnect_requested = True 1088 self.execute_item_lock.release() 1089 1090 def set_current_thread_and_frame(self, thread_id, frame_id, frame_kind): 1091 self.thread_id = thread_id 1092 self.frame_id = frame_id 1093 self.frame_kind = frame_kind 1094 self.exec_mod = None 1095 1096 def execute_code_work_item(self): 1097 if self.exec_mod is not None: 1098 BasicReplBackend.execute_code_work_item(self) 1099 else: 1100 try: 1101 if self.current_code and not self.current_code.isspace(): 1102 self.debugger.execute_code_no_report(self.current_code, self.thread_id, self.frame_id, self.frame_kind) 1103 finally: 1104 self.current_code = None 1105 1106 def get_members(self, expression): 1107 """returns a tuple of the type name, instance members, and type members""" 1108 if self.exec_mod is not None: 1109 return BasicReplBackend.get_members(self, expression) 1110 else: 1111 thread, cur_frame = self.debugger.get_thread_and_frame(self.thread_id, self.frame_id, self.frame_kind) 1112 return self.get_members_for_frame(expression, thread, cur_frame, self.frame_kind) 1113 1114 def get_signatures(self, expression): 1115 """returns doc, args, vargs, varkw, defaults.""" 1116 if self.exec_mod is not None: 1117 return BasicReplBackend.get_signatures(self, expression) 1118 else: 1119 thread, cur_frame = self.debugger.get_thread_and_frame(self.thread_id, self.frame_id, self.frame_kind) 1120 return self.get_signatures_for_frame(expression, thread, cur_frame, self.frame_kind) 1121 1122 def get_members_for_frame(self, expression, thread, cur_frame, frame_kind): 1123 """returns a tuple of the type name, instance members, and type members""" 1124 getattr_func = getattr 1125 if not expression: 1126 all_members = {} 1127 if sys.platform == 'cli': 1128 code = python_context.CreateSnippet('vars()', None, SourceCodeKind.AutoDetect) 1129 globals = code.Execute(Scope(cur_frame.f_globals)) 1130 locals = code.Execute(Scope(thread.get_locals(cur_frame, frame_kind))) 1131 else: 1132 globals = cur_frame.f_globals 1133 locals = thread.get_locals(cur_frame, frame_kind) 1134 1135 for key, value in globals.items(): 1136 all_members[key] = self.get_type_name(value) 1137 1138 for key, value in locals.items(): 1139 all_members[key] = self.get_type_name(value) 1140 1141 return '', all_members, {} 1142 else: 1143 if sys.platform == 'cli': 1144 scope = Scope(cur_frame.f_globals) 1145 1146 code = python_context.CreateSnippet(expression, None, SourceCodeKind.AutoDetect) 1147 val = code.Execute(scope) 1148 1149 code = python_context.CreateSnippet('dir(' + expression + ')', None, SourceCodeKind.AutoDetect) 1150 members = code.Execute(scope) 1151 1152 code = python_context.CreateSnippet('lambda value, name: getattr(value, name)', None, SourceCodeKind.AutoDetect) 1153 getattr_func = code.Execute(scope) 1154 else: 1155 val = eval(expression, cur_frame.f_globals, thread.get_locals(cur_frame, frame_kind)) 1156 members = dir(val) 1157 1158 return self.collect_members(val, members, getattr_func) 1159 1160 def get_signatures_for_frame(self, expression, thread, cur_frame, frame_kind): 1161 if sys.platform == 'cli': 1162 code = python_context.CreateSnippet(expression, None, SourceCodeKind.AutoDetect) 1163 val = code.Execute(Scope(cur_frame.f_globals)) 1164 else: 1165 val = eval(expression, cur_frame.f_globals, thread.get_locals(cur_frame, frame_kind)) 1166 1167 return self.collect_signatures(val) 1168 1169 def set_current_module(self, module): 1170 if module == '<CurrentFrame>': 1171 self.exec_mod = None 1172 else: 1173 BasicReplBackend.set_current_module(self, module) 1174 1175 def check_for_exit_repl_loop(self): 1176 return self.disconnect_requested 1177 1178 def check_for_exit_execution_loop(self): 1179 return self.disconnect_requested 1180 1181class _ReplOutput(object): 1182 """file like object which redirects output to the repl window.""" 1183 errors = None 1184 closed = False 1185 encoding = 'utf8' 1186 1187 def __init__(self, backend, is_stdout, old_out = None): 1188 self.name = "<stdout>" if is_stdout else "<stderr>" 1189 self.backend = backend 1190 self.old_out = old_out 1191 self.is_stdout = is_stdout 1192 self.pipe = None 1193 1194 def flush(self): 1195 if self.old_out: 1196 self.old_out.flush() 1197 1198 def fileno(self): 1199 if self.pipe is None: 1200 self.pipe = os.pipe() 1201 thread.start_new_thread(self.pipe_thread, (), {}) 1202 1203 return self.pipe[1] 1204 1205 def pipe_thread(self): 1206 while True: 1207 data = os.read(self.pipe[0], 1) 1208 if data == '\r': 1209 data = os.read(self.pipe[0], 1) 1210 if data == '\n': 1211 self.write('\n') 1212 else: 1213 self.write('\r' + data) 1214 else: 1215 self.write(data) 1216 1217 def writelines(self, lines): 1218 for line in lines: 1219 self.write(line) 1220 self.write('\n') 1221 1222 def write(self, value): 1223 _debug_write('printing ' + repr(value) + '\n') 1224 if self.is_stdout: 1225 self.backend.write_stdout(value) 1226 else: 1227 self.backend.write_stderr(value) 1228 if self.old_out: 1229 self.old_out.write(value) 1230 1231 def isatty(self): 1232 return True 1233 1234 def next(self): 1235 pass 1236 1237 1238class _ReplInput(object): 1239 """file like object which redirects input from the repl window""" 1240 def __init__(self, backend): 1241 self.backend = backend 1242 1243 def readline(self): 1244 return self.backend.read_line() 1245 1246 def readlines(self, size = None): 1247 res = [] 1248 while True: 1249 line = self.readline() 1250 if line is not None: 1251 res.append(line) 1252 else: 1253 break 1254 1255 return res 1256 1257 def xreadlines(self): 1258 return self 1259 1260 def write(self, *args): 1261 raise IOError("File not open for writing") 1262 1263 def flush(self): pass 1264 1265 def isatty(self): 1266 return True 1267 1268 def __iter__(self): 1269 return self 1270 1271 def next(self): 1272 return self.readline() 1273 1274 1275if sys.platform == 'cli': 1276 import System 1277 class DotNetOutput(System.IO.TextWriter): 1278 def __new__(cls, backend, is_stdout, old_out=None): 1279 return System.IO.TextWriter.__new__(cls) 1280 1281 def __init__(self, backend, is_stdout, old_out=None): 1282 self.backend = backend 1283 self.is_stdout = is_stdout 1284 self.old_out = old_out 1285 1286 def Write(self, value, *args): 1287 if self.old_out: 1288 self.old_out.Write(value, *args) 1289 1290 if not args: 1291 if type(value) is str or type(value) is System.Char: 1292 if self.is_stdout: 1293 self.backend.write_stdout(str(value).replace('\r\n', '\n')) 1294 else: 1295 self.backend.write_stderr(str(value).replace('\r\n', '\n')) 1296 else: 1297 super(DotNetOutput, self).Write.Overloads[object](value) 1298 else: 1299 self.Write(System.String.Format(value, *args)) 1300 1301 def WriteLine(self, value, *args): 1302 if self.old_out: 1303 self.old_out.WriteLine(value, *args) 1304 1305 if not args: 1306 if type(value) is str or type(value) is System.Char: 1307 if self.is_stdout: 1308 self.backend.write_stdout(str(value).replace('\r\n', '\n') + '\n') 1309 else: 1310 self.backend.write_stderr(str(value).replace('\r\n', '\n') + '\n') 1311 else: 1312 super(DotNetOutput, self).WriteLine.Overloads[object](value) 1313 else: 1314 self.WriteLine(System.String.Format(value, *args)) 1315 1316 @property 1317 def Encoding(self): 1318 return System.Text.Encoding.UTF8 1319 1320 1321BACKEND = None 1322 1323def _run_repl(): 1324 from optparse import OptionParser 1325 1326 parser = OptionParser(prog='repl', description='Process REPL options') 1327 parser.add_option('--port', dest='port', 1328 help='the port to connect back to') 1329 parser.add_option('--execution-mode', dest='backend', 1330 help='the backend to use') 1331 parser.add_option('--enable-attach', dest='enable_attach', 1332 action="store_true", default=False, 1333 help='enable attaching the debugger via $attach') 1334 1335 (options, args) = parser.parse_args() 1336 1337 # kick off repl 1338 # make us available under our "normal" name, not just __main__ which we'll likely replace. 1339 sys.modules['visualstudio_py_repl'] = sys.modules['__main__'] 1340 global __name__ 1341 __name__ = 'visualstudio_py_repl' 1342 1343 backend_type = BasicReplBackend 1344 backend_error = None 1345 if options.backend is not None and options.backend.lower() != 'standard': 1346 try: 1347 split_backend = options.backend.split('.') 1348 backend_mod_name = '.'.join(split_backend[:-1]) 1349 backend_name = split_backend[-1] 1350 backend_type = getattr(__import__(backend_mod_name), backend_name) 1351 except UnsupportedReplException: 1352 backend_error = sys.exc_info()[1].reason 1353 except: 1354 backend_error = traceback.format_exc() 1355 1356 # fix sys.path so that cwd is where the project lives. 1357 sys.path[0] = '.' 1358 # remove all of our parsed args in case we have a launch file that cares... 1359 sys.argv = args or [''] 1360 1361 global BACKEND 1362 try: 1363 BACKEND = backend_type() 1364 except UnsupportedReplException: 1365 backend_error = sys.exc_info()[1].reason 1366 BACKEND = BasicReplBackend() 1367 except Exception: 1368 backend_error = traceback.format_exc() 1369 BACKEND = BasicReplBackend() 1370 BACKEND.connect(int(options.port)) 1371 1372 if options.enable_attach: 1373 BACKEND.init_debugger() 1374 1375 if backend_error is not None: 1376 sys.stderr.write('Error using selected REPL back-end:\n') 1377 sys.stderr.write(backend_error + '\n') 1378 sys.stderr.write('Using standard backend instead\n') 1379 1380 # execute code on the main thread which we can interrupt 1381 BACKEND.execution_loop() 1382 1383if __name__ == '__main__': 1384 try: 1385 _run_repl() 1386 except: 1387 if DEBUG: 1388 _debug_write(traceback.format_exc()) 1389 _debug_write('exiting') 1390 input() 1391 raise 1392