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