Home | History | Annotate | Download | only in idlelib
      1 import io
      2 import linecache
      3 import queue
      4 import sys
      5 import time
      6 import traceback
      7 import _thread as thread
      8 import threading
      9 import warnings
     10 
     11 import tkinter  # Tcl, deletions, messagebox if startup fails
     12 
     13 from idlelib import autocomplete  # AutoComplete, fetch_encodings
     14 from idlelib import calltips  # CallTips
     15 from idlelib import debugger_r  # start_debugger
     16 from idlelib import debugobj_r  # remote_object_tree_item
     17 from idlelib import iomenu  # encoding
     18 from idlelib import rpc  # multiple objects
     19 from idlelib import stackviewer  # StackTreeItem
     20 import __main__
     21 
     22 for mod in ('simpledialog', 'messagebox', 'font',
     23             'dialog', 'filedialog', 'commondialog',
     24             'ttk'):
     25     delattr(tkinter, mod)
     26     del sys.modules['tkinter.' + mod]
     27 
     28 LOCALHOST = '127.0.0.1'
     29 
     30 
     31 def idle_formatwarning(message, category, filename, lineno, line=None):
     32     """Format warnings the IDLE way."""
     33 
     34     s = "\nWarning (from warnings module):\n"
     35     s += '  File \"%s\", line %s\n' % (filename, lineno)
     36     if line is None:
     37         line = linecache.getline(filename, lineno)
     38     line = line.strip()
     39     if line:
     40         s += "    %s\n" % line
     41     s += "%s: %s\n" % (category.__name__, message)
     42     return s
     43 
     44 def idle_showwarning_subproc(
     45         message, category, filename, lineno, file=None, line=None):
     46     """Show Idle-format warning after replacing warnings.showwarning.
     47 
     48     The only difference is the formatter called.
     49     """
     50     if file is None:
     51         file = sys.stderr
     52     try:
     53         file.write(idle_formatwarning(
     54                 message, category, filename, lineno, line))
     55     except IOError:
     56         pass # the file (probably stderr) is invalid - this warning gets lost.
     57 
     58 _warnings_showwarning = None
     59 
     60 def capture_warnings(capture):
     61     "Replace warning.showwarning with idle_showwarning_subproc, or reverse."
     62 
     63     global _warnings_showwarning
     64     if capture:
     65         if _warnings_showwarning is None:
     66             _warnings_showwarning = warnings.showwarning
     67             warnings.showwarning = idle_showwarning_subproc
     68     else:
     69         if _warnings_showwarning is not None:
     70             warnings.showwarning = _warnings_showwarning
     71             _warnings_showwarning = None
     72 
     73 capture_warnings(True)
     74 tcl = tkinter.Tcl()
     75 
     76 def handle_tk_events(tcl=tcl):
     77     """Process any tk events that are ready to be dispatched if tkinter
     78     has been imported, a tcl interpreter has been created and tk has been
     79     loaded."""
     80     tcl.eval("update")
     81 
     82 # Thread shared globals: Establish a queue between a subthread (which handles
     83 # the socket) and the main thread (which runs user code), plus global
     84 # completion, exit and interruptable (the main thread) flags:
     85 
     86 exit_now = False
     87 quitting = False
     88 interruptable = False
     89 
     90 def main(del_exitfunc=False):
     91     """Start the Python execution server in a subprocess
     92 
     93     In the Python subprocess, RPCServer is instantiated with handlerclass
     94     MyHandler, which inherits register/unregister methods from RPCHandler via
     95     the mix-in class SocketIO.
     96 
     97     When the RPCServer 'server' is instantiated, the TCPServer initialization
     98     creates an instance of run.MyHandler and calls its handle() method.
     99     handle() instantiates a run.Executive object, passing it a reference to the
    100     MyHandler object.  That reference is saved as attribute rpchandler of the
    101     Executive instance.  The Executive methods have access to the reference and
    102     can pass it on to entities that they command
    103     (e.g. debugger_r.Debugger.start_debugger()).  The latter, in turn, can
    104     call MyHandler(SocketIO) register/unregister methods via the reference to
    105     register and unregister themselves.
    106 
    107     """
    108     global exit_now
    109     global quitting
    110     global no_exitfunc
    111     no_exitfunc = del_exitfunc
    112     #time.sleep(15) # test subprocess not responding
    113     try:
    114         assert(len(sys.argv) > 1)
    115         port = int(sys.argv[-1])
    116     except:
    117         print("IDLE Subprocess: no IP port passed in sys.argv.",
    118               file=sys.__stderr__)
    119         return
    120 
    121     capture_warnings(True)
    122     sys.argv[:] = [""]
    123     sockthread = threading.Thread(target=manage_socket,
    124                                   name='SockThread',
    125                                   args=((LOCALHOST, port),))
    126     sockthread.daemon = True
    127     sockthread.start()
    128     while 1:
    129         try:
    130             if exit_now:
    131                 try:
    132                     exit()
    133                 except KeyboardInterrupt:
    134                     # exiting but got an extra KBI? Try again!
    135                     continue
    136             try:
    137                 seq, request = rpc.request_queue.get(block=True, timeout=0.05)
    138             except queue.Empty:
    139                 handle_tk_events()
    140                 continue
    141             method, args, kwargs = request
    142             ret = method(*args, **kwargs)
    143             rpc.response_queue.put((seq, ret))
    144         except KeyboardInterrupt:
    145             if quitting:
    146                 exit_now = True
    147             continue
    148         except SystemExit:
    149             capture_warnings(False)
    150             raise
    151         except:
    152             type, value, tb = sys.exc_info()
    153             try:
    154                 print_exception()
    155                 rpc.response_queue.put((seq, None))
    156             except:
    157                 # Link didn't work, print same exception to __stderr__
    158                 traceback.print_exception(type, value, tb, file=sys.__stderr__)
    159                 exit()
    160             else:
    161                 continue
    162 
    163 def manage_socket(address):
    164     for i in range(3):
    165         time.sleep(i)
    166         try:
    167             server = MyRPCServer(address, MyHandler)
    168             break
    169         except OSError as err:
    170             print("IDLE Subprocess: OSError: " + err.args[1] +
    171                   ", retrying....", file=sys.__stderr__)
    172             socket_error = err
    173     else:
    174         print("IDLE Subprocess: Connection to "
    175               "IDLE GUI failed, exiting.", file=sys.__stderr__)
    176         show_socket_error(socket_error, address)
    177         global exit_now
    178         exit_now = True
    179         return
    180     server.handle_request() # A single request only
    181 
    182 def show_socket_error(err, address):
    183     import tkinter
    184     import tkinter.messagebox as tkMessageBox
    185     root = tkinter.Tk()
    186     root.withdraw()
    187     if err.args[0] == 61: # connection refused
    188         msg = "IDLE's subprocess can't connect to %s:%d.  This may be due "\
    189               "to your personal firewall configuration.  It is safe to "\
    190               "allow this internal connection because no data is visible on "\
    191               "external ports." % address
    192         tkMessageBox.showerror("IDLE Subprocess Error", msg, parent=root)
    193     else:
    194         tkMessageBox.showerror("IDLE Subprocess Error",
    195                                "Socket Error: %s" % err.args[1], parent=root)
    196     root.destroy()
    197 
    198 def print_exception():
    199     import linecache
    200     linecache.checkcache()
    201     flush_stdout()
    202     efile = sys.stderr
    203     typ, val, tb = excinfo = sys.exc_info()
    204     sys.last_type, sys.last_value, sys.last_traceback = excinfo
    205     seen = set()
    206 
    207     def print_exc(typ, exc, tb):
    208         seen.add(exc)
    209         context = exc.__context__
    210         cause = exc.__cause__
    211         if cause is not None and cause not in seen:
    212             print_exc(type(cause), cause, cause.__traceback__)
    213             print("\nThe above exception was the direct cause "
    214                   "of the following exception:\n", file=efile)
    215         elif (context is not None and
    216               not exc.__suppress_context__ and
    217               context not in seen):
    218             print_exc(type(context), context, context.__traceback__)
    219             print("\nDuring handling of the above exception, "
    220                   "another exception occurred:\n", file=efile)
    221         if tb:
    222             tbe = traceback.extract_tb(tb)
    223             print('Traceback (most recent call last):', file=efile)
    224             exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
    225                        "debugger_r.py", "bdb.py")
    226             cleanup_traceback(tbe, exclude)
    227             traceback.print_list(tbe, file=efile)
    228         lines = traceback.format_exception_only(typ, exc)
    229         for line in lines:
    230             print(line, end='', file=efile)
    231 
    232     print_exc(typ, val, tb)
    233 
    234 def cleanup_traceback(tb, exclude):
    235     "Remove excluded traces from beginning/end of tb; get cached lines"
    236     orig_tb = tb[:]
    237     while tb:
    238         for rpcfile in exclude:
    239             if tb[0][0].count(rpcfile):
    240                 break    # found an exclude, break for: and delete tb[0]
    241         else:
    242             break        # no excludes, have left RPC code, break while:
    243         del tb[0]
    244     while tb:
    245         for rpcfile in exclude:
    246             if tb[-1][0].count(rpcfile):
    247                 break
    248         else:
    249             break
    250         del tb[-1]
    251     if len(tb) == 0:
    252         # exception was in IDLE internals, don't prune!
    253         tb[:] = orig_tb[:]
    254         print("** IDLE Internal Exception: ", file=sys.stderr)
    255     rpchandler = rpc.objecttable['exec'].rpchandler
    256     for i in range(len(tb)):
    257         fn, ln, nm, line = tb[i]
    258         if nm == '?':
    259             nm = "-toplevel-"
    260         if not line and fn.startswith("<pyshell#"):
    261             line = rpchandler.remotecall('linecache', 'getline',
    262                                               (fn, ln), {})
    263         tb[i] = fn, ln, nm, line
    264 
    265 def flush_stdout():
    266     """XXX How to do this now?"""
    267 
    268 def exit():
    269     """Exit subprocess, possibly after first clearing exit functions.
    270 
    271     If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any
    272     functions registered with atexit will be removed before exiting.
    273     (VPython support)
    274 
    275     """
    276     if no_exitfunc:
    277         import atexit
    278         atexit._clear()
    279     capture_warnings(False)
    280     sys.exit(0)
    281 
    282 
    283 class MyRPCServer(rpc.RPCServer):
    284 
    285     def handle_error(self, request, client_address):
    286         """Override RPCServer method for IDLE
    287 
    288         Interrupt the MainThread and exit server if link is dropped.
    289 
    290         """
    291         global quitting
    292         try:
    293             raise
    294         except SystemExit:
    295             raise
    296         except EOFError:
    297             global exit_now
    298             exit_now = True
    299             thread.interrupt_main()
    300         except:
    301             erf = sys.__stderr__
    302             print('\n' + '-'*40, file=erf)
    303             print('Unhandled server exception!', file=erf)
    304             print('Thread: %s' % threading.current_thread().name, file=erf)
    305             print('Client Address: ', client_address, file=erf)
    306             print('Request: ', repr(request), file=erf)
    307             traceback.print_exc(file=erf)
    308             print('\n*** Unrecoverable, server exiting!', file=erf)
    309             print('-'*40, file=erf)
    310             quitting = True
    311             thread.interrupt_main()
    312 
    313 
    314 # Pseudofiles for shell-remote communication (also used in pyshell)
    315 
    316 class PseudoFile(io.TextIOBase):
    317 
    318     def __init__(self, shell, tags, encoding=None):
    319         self.shell = shell
    320         self.tags = tags
    321         self._encoding = encoding
    322 
    323     @property
    324     def encoding(self):
    325         return self._encoding
    326 
    327     @property
    328     def name(self):
    329         return '<%s>' % self.tags
    330 
    331     def isatty(self):
    332         return True
    333 
    334 
    335 class PseudoOutputFile(PseudoFile):
    336 
    337     def writable(self):
    338         return True
    339 
    340     def write(self, s):
    341         if self.closed:
    342             raise ValueError("write to closed file")
    343         if type(s) is not str:
    344             if not isinstance(s, str):
    345                 raise TypeError('must be str, not ' + type(s).__name__)
    346             # See issue #19481
    347             s = str.__str__(s)
    348         return self.shell.write(s, self.tags)
    349 
    350 
    351 class PseudoInputFile(PseudoFile):
    352 
    353     def __init__(self, shell, tags, encoding=None):
    354         PseudoFile.__init__(self, shell, tags, encoding)
    355         self._line_buffer = ''
    356 
    357     def readable(self):
    358         return True
    359 
    360     def read(self, size=-1):
    361         if self.closed:
    362             raise ValueError("read from closed file")
    363         if size is None:
    364             size = -1
    365         elif not isinstance(size, int):
    366             raise TypeError('must be int, not ' + type(size).__name__)
    367         result = self._line_buffer
    368         self._line_buffer = ''
    369         if size < 0:
    370             while True:
    371                 line = self.shell.readline()
    372                 if not line: break
    373                 result += line
    374         else:
    375             while len(result) < size:
    376                 line = self.shell.readline()
    377                 if not line: break
    378                 result += line
    379             self._line_buffer = result[size:]
    380             result = result[:size]
    381         return result
    382 
    383     def readline(self, size=-1):
    384         if self.closed:
    385             raise ValueError("read from closed file")
    386         if size is None:
    387             size = -1
    388         elif not isinstance(size, int):
    389             raise TypeError('must be int, not ' + type(size).__name__)
    390         line = self._line_buffer or self.shell.readline()
    391         if size < 0:
    392             size = len(line)
    393         eol = line.find('\n', 0, size)
    394         if eol >= 0:
    395             size = eol + 1
    396         self._line_buffer = line[size:]
    397         return line[:size]
    398 
    399     def close(self):
    400         self.shell.close()
    401 
    402 
    403 class MyHandler(rpc.RPCHandler):
    404 
    405     def handle(self):
    406         """Override base method"""
    407         executive = Executive(self)
    408         self.register("exec", executive)
    409         self.console = self.get_remote_proxy("console")
    410         sys.stdin = PseudoInputFile(self.console, "stdin",
    411                 iomenu.encoding)
    412         sys.stdout = PseudoOutputFile(self.console, "stdout",
    413                 iomenu.encoding)
    414         sys.stderr = PseudoOutputFile(self.console, "stderr",
    415                 iomenu.encoding)
    416 
    417         sys.displayhook = rpc.displayhook
    418         # page help() text to shell.
    419         import pydoc # import must be done here to capture i/o binding
    420         pydoc.pager = pydoc.plainpager
    421 
    422         # Keep a reference to stdin so that it won't try to exit IDLE if
    423         # sys.stdin gets changed from within IDLE's shell. See issue17838.
    424         self._keep_stdin = sys.stdin
    425 
    426         self.interp = self.get_remote_proxy("interp")
    427         rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05)
    428 
    429     def exithook(self):
    430         "override SocketIO method - wait for MainThread to shut us down"
    431         time.sleep(10)
    432 
    433     def EOFhook(self):
    434         "Override SocketIO method - terminate wait on callback and exit thread"
    435         global quitting
    436         quitting = True
    437         thread.interrupt_main()
    438 
    439     def decode_interrupthook(self):
    440         "interrupt awakened thread"
    441         global quitting
    442         quitting = True
    443         thread.interrupt_main()
    444 
    445 
    446 class Executive(object):
    447 
    448     def __init__(self, rpchandler):
    449         self.rpchandler = rpchandler
    450         self.locals = __main__.__dict__
    451         self.calltip = calltips.CallTips()
    452         self.autocomplete = autocomplete.AutoComplete()
    453 
    454     def runcode(self, code):
    455         global interruptable
    456         try:
    457             self.usr_exc_info = None
    458             interruptable = True
    459             try:
    460                 exec(code, self.locals)
    461             finally:
    462                 interruptable = False
    463         except SystemExit:
    464             # Scripts that raise SystemExit should just
    465             # return to the interactive prompt
    466             pass
    467         except:
    468             self.usr_exc_info = sys.exc_info()
    469             if quitting:
    470                 exit()
    471             # even print a user code SystemExit exception, continue
    472             print_exception()
    473             jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>")
    474             if jit:
    475                 self.rpchandler.interp.open_remote_stack_viewer()
    476         else:
    477             flush_stdout()
    478 
    479     def interrupt_the_server(self):
    480         if interruptable:
    481             thread.interrupt_main()
    482 
    483     def start_the_debugger(self, gui_adap_oid):
    484         return debugger_r.start_debugger(self.rpchandler, gui_adap_oid)
    485 
    486     def stop_the_debugger(self, idb_adap_oid):
    487         "Unregister the Idb Adapter.  Link objects and Idb then subject to GC"
    488         self.rpchandler.unregister(idb_adap_oid)
    489 
    490     def get_the_calltip(self, name):
    491         return self.calltip.fetch_tip(name)
    492 
    493     def get_the_completion_list(self, what, mode):
    494         return self.autocomplete.fetch_completions(what, mode)
    495 
    496     def stackviewer(self, flist_oid=None):
    497         if self.usr_exc_info:
    498             typ, val, tb = self.usr_exc_info
    499         else:
    500             return None
    501         flist = None
    502         if flist_oid is not None:
    503             flist = self.rpchandler.get_remote_proxy(flist_oid)
    504         while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
    505             tb = tb.tb_next
    506         sys.last_type = typ
    507         sys.last_value = val
    508         item = stackviewer.StackTreeItem(flist, tb)
    509         return debugobj_r.remote_object_tree_item(item)
    510 
    511 capture_warnings(False)  # Make sure turned off; see issue 18081
    512