1""" idlelib.run 2 3Simplified, pyshell.ModifiedInterpreter spawns a subprocess with 4f'''{sys.executable} -c "__import__('idlelib.run').run.main()"''' 5'.run' is needed because __import__ returns idlelib, not idlelib.run. 6""" 7import functools 8import io 9import linecache 10import queue 11import sys 12import textwrap 13import time 14import traceback 15import _thread as thread 16import threading 17import warnings 18 19import idlelib # testing 20from idlelib import autocomplete # AutoComplete, fetch_encodings 21from idlelib import calltip # Calltip 22from idlelib import debugger_r # start_debugger 23from idlelib import debugobj_r # remote_object_tree_item 24from idlelib import iomenu # encoding 25from idlelib import rpc # multiple objects 26from idlelib import stackviewer # StackTreeItem 27import __main__ 28 29import tkinter # Use tcl and, if startup fails, messagebox. 30if not hasattr(sys.modules['idlelib.run'], 'firstrun'): 31 # Undo modifications of tkinter by idlelib imports; see bpo-25507. 32 for mod in ('simpledialog', 'messagebox', 'font', 33 'dialog', 'filedialog', 'commondialog', 34 'ttk'): 35 delattr(tkinter, mod) 36 del sys.modules['tkinter.' + mod] 37 # Avoid AttributeError if run again; see bpo-37038. 38 sys.modules['idlelib.run'].firstrun = False 39 40LOCALHOST = '127.0.0.1' 41 42 43def idle_formatwarning(message, category, filename, lineno, line=None): 44 """Format warnings the IDLE way.""" 45 46 s = "\nWarning (from warnings module):\n" 47 s += ' File \"%s\", line %s\n' % (filename, lineno) 48 if line is None: 49 line = linecache.getline(filename, lineno) 50 line = line.strip() 51 if line: 52 s += " %s\n" % line 53 s += "%s: %s\n" % (category.__name__, message) 54 return s 55 56def idle_showwarning_subproc( 57 message, category, filename, lineno, file=None, line=None): 58 """Show Idle-format warning after replacing warnings.showwarning. 59 60 The only difference is the formatter called. 61 """ 62 if file is None: 63 file = sys.stderr 64 try: 65 file.write(idle_formatwarning( 66 message, category, filename, lineno, line)) 67 except OSError: 68 pass # the file (probably stderr) is invalid - this warning gets lost. 69 70_warnings_showwarning = None 71 72def capture_warnings(capture): 73 "Replace warning.showwarning with idle_showwarning_subproc, or reverse." 74 75 global _warnings_showwarning 76 if capture: 77 if _warnings_showwarning is None: 78 _warnings_showwarning = warnings.showwarning 79 warnings.showwarning = idle_showwarning_subproc 80 else: 81 if _warnings_showwarning is not None: 82 warnings.showwarning = _warnings_showwarning 83 _warnings_showwarning = None 84 85capture_warnings(True) 86tcl = tkinter.Tcl() 87 88def handle_tk_events(tcl=tcl): 89 """Process any tk events that are ready to be dispatched if tkinter 90 has been imported, a tcl interpreter has been created and tk has been 91 loaded.""" 92 tcl.eval("update") 93 94# Thread shared globals: Establish a queue between a subthread (which handles 95# the socket) and the main thread (which runs user code), plus global 96# completion, exit and interruptable (the main thread) flags: 97 98exit_now = False 99quitting = False 100interruptable = False 101 102def main(del_exitfunc=False): 103 """Start the Python execution server in a subprocess 104 105 In the Python subprocess, RPCServer is instantiated with handlerclass 106 MyHandler, which inherits register/unregister methods from RPCHandler via 107 the mix-in class SocketIO. 108 109 When the RPCServer 'server' is instantiated, the TCPServer initialization 110 creates an instance of run.MyHandler and calls its handle() method. 111 handle() instantiates a run.Executive object, passing it a reference to the 112 MyHandler object. That reference is saved as attribute rpchandler of the 113 Executive instance. The Executive methods have access to the reference and 114 can pass it on to entities that they command 115 (e.g. debugger_r.Debugger.start_debugger()). The latter, in turn, can 116 call MyHandler(SocketIO) register/unregister methods via the reference to 117 register and unregister themselves. 118 119 """ 120 global exit_now 121 global quitting 122 global no_exitfunc 123 no_exitfunc = del_exitfunc 124 #time.sleep(15) # test subprocess not responding 125 try: 126 assert(len(sys.argv) > 1) 127 port = int(sys.argv[-1]) 128 except: 129 print("IDLE Subprocess: no IP port passed in sys.argv.", 130 file=sys.__stderr__) 131 return 132 133 capture_warnings(True) 134 sys.argv[:] = [""] 135 sockthread = threading.Thread(target=manage_socket, 136 name='SockThread', 137 args=((LOCALHOST, port),)) 138 sockthread.daemon = True 139 sockthread.start() 140 while 1: 141 try: 142 if exit_now: 143 try: 144 exit() 145 except KeyboardInterrupt: 146 # exiting but got an extra KBI? Try again! 147 continue 148 try: 149 request = rpc.request_queue.get(block=True, timeout=0.05) 150 except queue.Empty: 151 request = None 152 # Issue 32207: calling handle_tk_events here adds spurious 153 # queue.Empty traceback to event handling exceptions. 154 if request: 155 seq, (method, args, kwargs) = request 156 ret = method(*args, **kwargs) 157 rpc.response_queue.put((seq, ret)) 158 else: 159 handle_tk_events() 160 except KeyboardInterrupt: 161 if quitting: 162 exit_now = True 163 continue 164 except SystemExit: 165 capture_warnings(False) 166 raise 167 except: 168 type, value, tb = sys.exc_info() 169 try: 170 print_exception() 171 rpc.response_queue.put((seq, None)) 172 except: 173 # Link didn't work, print same exception to __stderr__ 174 traceback.print_exception(type, value, tb, file=sys.__stderr__) 175 exit() 176 else: 177 continue 178 179def manage_socket(address): 180 for i in range(3): 181 time.sleep(i) 182 try: 183 server = MyRPCServer(address, MyHandler) 184 break 185 except OSError as err: 186 print("IDLE Subprocess: OSError: " + err.args[1] + 187 ", retrying....", file=sys.__stderr__) 188 socket_error = err 189 else: 190 print("IDLE Subprocess: Connection to " 191 "IDLE GUI failed, exiting.", file=sys.__stderr__) 192 show_socket_error(socket_error, address) 193 global exit_now 194 exit_now = True 195 return 196 server.handle_request() # A single request only 197 198def show_socket_error(err, address): 199 "Display socket error from manage_socket." 200 import tkinter 201 from tkinter.messagebox import showerror 202 root = tkinter.Tk() 203 fix_scaling(root) 204 root.withdraw() 205 showerror( 206 "Subprocess Connection Error", 207 f"IDLE's subprocess can't connect to {address[0]}:{address[1]}.\n" 208 f"Fatal OSError #{err.errno}: {err.strerror}.\n" 209 "See the 'Startup failure' section of the IDLE doc, online at\n" 210 "https://docs.python.org/3/library/idle.html#startup-failure", 211 parent=root) 212 root.destroy() 213 214def print_exception(): 215 import linecache 216 linecache.checkcache() 217 flush_stdout() 218 efile = sys.stderr 219 typ, val, tb = excinfo = sys.exc_info() 220 sys.last_type, sys.last_value, sys.last_traceback = excinfo 221 seen = set() 222 223 def print_exc(typ, exc, tb): 224 seen.add(id(exc)) 225 context = exc.__context__ 226 cause = exc.__cause__ 227 if cause is not None and id(cause) not in seen: 228 print_exc(type(cause), cause, cause.__traceback__) 229 print("\nThe above exception was the direct cause " 230 "of the following exception:\n", file=efile) 231 elif (context is not None and 232 not exc.__suppress_context__ and 233 id(context) not in seen): 234 print_exc(type(context), context, context.__traceback__) 235 print("\nDuring handling of the above exception, " 236 "another exception occurred:\n", file=efile) 237 if tb: 238 tbe = traceback.extract_tb(tb) 239 print('Traceback (most recent call last):', file=efile) 240 exclude = ("run.py", "rpc.py", "threading.py", "queue.py", 241 "debugger_r.py", "bdb.py") 242 cleanup_traceback(tbe, exclude) 243 traceback.print_list(tbe, file=efile) 244 lines = traceback.format_exception_only(typ, exc) 245 for line in lines: 246 print(line, end='', file=efile) 247 248 print_exc(typ, val, tb) 249 250def cleanup_traceback(tb, exclude): 251 "Remove excluded traces from beginning/end of tb; get cached lines" 252 orig_tb = tb[:] 253 while tb: 254 for rpcfile in exclude: 255 if tb[0][0].count(rpcfile): 256 break # found an exclude, break for: and delete tb[0] 257 else: 258 break # no excludes, have left RPC code, break while: 259 del tb[0] 260 while tb: 261 for rpcfile in exclude: 262 if tb[-1][0].count(rpcfile): 263 break 264 else: 265 break 266 del tb[-1] 267 if len(tb) == 0: 268 # exception was in IDLE internals, don't prune! 269 tb[:] = orig_tb[:] 270 print("** IDLE Internal Exception: ", file=sys.stderr) 271 rpchandler = rpc.objecttable['exec'].rpchandler 272 for i in range(len(tb)): 273 fn, ln, nm, line = tb[i] 274 if nm == '?': 275 nm = "-toplevel-" 276 if not line and fn.startswith("<pyshell#"): 277 line = rpchandler.remotecall('linecache', 'getline', 278 (fn, ln), {}) 279 tb[i] = fn, ln, nm, line 280 281def flush_stdout(): 282 """XXX How to do this now?""" 283 284def exit(): 285 """Exit subprocess, possibly after first clearing exit functions. 286 287 If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any 288 functions registered with atexit will be removed before exiting. 289 (VPython support) 290 291 """ 292 if no_exitfunc: 293 import atexit 294 atexit._clear() 295 capture_warnings(False) 296 sys.exit(0) 297 298 299def fix_scaling(root): 300 """Scale fonts on HiDPI displays.""" 301 import tkinter.font 302 scaling = float(root.tk.call('tk', 'scaling')) 303 if scaling > 1.4: 304 for name in tkinter.font.names(root): 305 font = tkinter.font.Font(root=root, name=name, exists=True) 306 size = int(font['size']) 307 if size < 0: 308 font['size'] = round(-0.75*size) 309 310 311def fixdoc(fun, text): 312 tem = (fun.__doc__ + '\n\n') if fun.__doc__ is not None else '' 313 fun.__doc__ = tem + textwrap.fill(textwrap.dedent(text)) 314 315RECURSIONLIMIT_DELTA = 30 316 317def install_recursionlimit_wrappers(): 318 """Install wrappers to always add 30 to the recursion limit.""" 319 # see: bpo-26806 320 321 @functools.wraps(sys.setrecursionlimit) 322 def setrecursionlimit(*args, **kwargs): 323 # mimic the original sys.setrecursionlimit()'s input handling 324 if kwargs: 325 raise TypeError( 326 "setrecursionlimit() takes no keyword arguments") 327 try: 328 limit, = args 329 except ValueError: 330 raise TypeError(f"setrecursionlimit() takes exactly one " 331 f"argument ({len(args)} given)") 332 if not limit > 0: 333 raise ValueError( 334 "recursion limit must be greater or equal than 1") 335 336 return setrecursionlimit.__wrapped__(limit + RECURSIONLIMIT_DELTA) 337 338 fixdoc(setrecursionlimit, f"""\ 339 This IDLE wrapper adds {RECURSIONLIMIT_DELTA} to prevent possible 340 uninterruptible loops.""") 341 342 @functools.wraps(sys.getrecursionlimit) 343 def getrecursionlimit(): 344 return getrecursionlimit.__wrapped__() - RECURSIONLIMIT_DELTA 345 346 fixdoc(getrecursionlimit, f"""\ 347 This IDLE wrapper subtracts {RECURSIONLIMIT_DELTA} to compensate 348 for the {RECURSIONLIMIT_DELTA} IDLE adds when setting the limit.""") 349 350 # add the delta to the default recursion limit, to compensate 351 sys.setrecursionlimit(sys.getrecursionlimit() + RECURSIONLIMIT_DELTA) 352 353 sys.setrecursionlimit = setrecursionlimit 354 sys.getrecursionlimit = getrecursionlimit 355 356 357def uninstall_recursionlimit_wrappers(): 358 """Uninstall the recursion limit wrappers from the sys module. 359 360 IDLE only uses this for tests. Users can import run and call 361 this to remove the wrapping. 362 """ 363 if ( 364 getattr(sys.setrecursionlimit, '__wrapped__', None) and 365 getattr(sys.getrecursionlimit, '__wrapped__', None) 366 ): 367 sys.setrecursionlimit = sys.setrecursionlimit.__wrapped__ 368 sys.getrecursionlimit = sys.getrecursionlimit.__wrapped__ 369 sys.setrecursionlimit(sys.getrecursionlimit() - RECURSIONLIMIT_DELTA) 370 371 372class MyRPCServer(rpc.RPCServer): 373 374 def handle_error(self, request, client_address): 375 """Override RPCServer method for IDLE 376 377 Interrupt the MainThread and exit server if link is dropped. 378 379 """ 380 global quitting 381 try: 382 raise 383 except SystemExit: 384 raise 385 except EOFError: 386 global exit_now 387 exit_now = True 388 thread.interrupt_main() 389 except: 390 erf = sys.__stderr__ 391 print(textwrap.dedent(f""" 392 {'-'*40} 393 Unhandled exception in user code execution server!' 394 Thread: {threading.current_thread().name} 395 IDLE Client Address: {client_address} 396 Request: {request!r} 397 """), file=erf) 398 traceback.print_exc(limit=-20, file=erf) 399 print(textwrap.dedent(f""" 400 *** Unrecoverable, server exiting! 401 402 Users should never see this message; it is likely transient. 403 If this recurs, report this with a copy of the message 404 and an explanation of how to make it repeat. 405 {'-'*40}"""), file=erf) 406 quitting = True 407 thread.interrupt_main() 408 409 410# Pseudofiles for shell-remote communication (also used in pyshell) 411 412class StdioFile(io.TextIOBase): 413 414 def __init__(self, shell, tags, encoding='utf-8', errors='strict'): 415 self.shell = shell 416 self.tags = tags 417 self._encoding = encoding 418 self._errors = errors 419 420 @property 421 def encoding(self): 422 return self._encoding 423 424 @property 425 def errors(self): 426 return self._errors 427 428 @property 429 def name(self): 430 return '<%s>' % self.tags 431 432 def isatty(self): 433 return True 434 435 436class StdOutputFile(StdioFile): 437 438 def writable(self): 439 return True 440 441 def write(self, s): 442 if self.closed: 443 raise ValueError("write to closed file") 444 s = str.encode(s, self.encoding, self.errors).decode(self.encoding, self.errors) 445 return self.shell.write(s, self.tags) 446 447 448class StdInputFile(StdioFile): 449 _line_buffer = '' 450 451 def readable(self): 452 return True 453 454 def read(self, size=-1): 455 if self.closed: 456 raise ValueError("read from closed file") 457 if size is None: 458 size = -1 459 elif not isinstance(size, int): 460 raise TypeError('must be int, not ' + type(size).__name__) 461 result = self._line_buffer 462 self._line_buffer = '' 463 if size < 0: 464 while True: 465 line = self.shell.readline() 466 if not line: break 467 result += line 468 else: 469 while len(result) < size: 470 line = self.shell.readline() 471 if not line: break 472 result += line 473 self._line_buffer = result[size:] 474 result = result[:size] 475 return result 476 477 def readline(self, size=-1): 478 if self.closed: 479 raise ValueError("read from closed file") 480 if size is None: 481 size = -1 482 elif not isinstance(size, int): 483 raise TypeError('must be int, not ' + type(size).__name__) 484 line = self._line_buffer or self.shell.readline() 485 if size < 0: 486 size = len(line) 487 eol = line.find('\n', 0, size) 488 if eol >= 0: 489 size = eol + 1 490 self._line_buffer = line[size:] 491 return line[:size] 492 493 def close(self): 494 self.shell.close() 495 496 497class MyHandler(rpc.RPCHandler): 498 499 def handle(self): 500 """Override base method""" 501 executive = Executive(self) 502 self.register("exec", executive) 503 self.console = self.get_remote_proxy("console") 504 sys.stdin = StdInputFile(self.console, "stdin", 505 iomenu.encoding, iomenu.errors) 506 sys.stdout = StdOutputFile(self.console, "stdout", 507 iomenu.encoding, iomenu.errors) 508 sys.stderr = StdOutputFile(self.console, "stderr", 509 iomenu.encoding, "backslashreplace") 510 511 sys.displayhook = rpc.displayhook 512 # page help() text to shell. 513 import pydoc # import must be done here to capture i/o binding 514 pydoc.pager = pydoc.plainpager 515 516 # Keep a reference to stdin so that it won't try to exit IDLE if 517 # sys.stdin gets changed from within IDLE's shell. See issue17838. 518 self._keep_stdin = sys.stdin 519 520 install_recursionlimit_wrappers() 521 522 self.interp = self.get_remote_proxy("interp") 523 rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05) 524 525 def exithook(self): 526 "override SocketIO method - wait for MainThread to shut us down" 527 time.sleep(10) 528 529 def EOFhook(self): 530 "Override SocketIO method - terminate wait on callback and exit thread" 531 global quitting 532 quitting = True 533 thread.interrupt_main() 534 535 def decode_interrupthook(self): 536 "interrupt awakened thread" 537 global quitting 538 quitting = True 539 thread.interrupt_main() 540 541 542class Executive: 543 544 def __init__(self, rpchandler): 545 self.rpchandler = rpchandler 546 if idlelib.testing is False: 547 self.locals = __main__.__dict__ 548 self.calltip = calltip.Calltip() 549 self.autocomplete = autocomplete.AutoComplete() 550 else: 551 self.locals = {} 552 553 def runcode(self, code): 554 global interruptable 555 try: 556 self.user_exc_info = None 557 interruptable = True 558 try: 559 exec(code, self.locals) 560 finally: 561 interruptable = False 562 except SystemExit as e: 563 if e.args: # SystemExit called with an argument. 564 ob = e.args[0] 565 if not isinstance(ob, (type(None), int)): 566 print('SystemExit: ' + str(ob), file=sys.stderr) 567 # Return to the interactive prompt. 568 except: 569 self.user_exc_info = sys.exc_info() # For testing, hook, viewer. 570 if quitting: 571 exit() 572 if sys.excepthook is sys.__excepthook__: 573 print_exception() 574 else: 575 try: 576 sys.excepthook(*self.user_exc_info) 577 except: 578 self.user_exc_info = sys.exc_info() # For testing. 579 print_exception() 580 jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>") 581 if jit: 582 self.rpchandler.interp.open_remote_stack_viewer() 583 else: 584 flush_stdout() 585 586 def interrupt_the_server(self): 587 if interruptable: 588 thread.interrupt_main() 589 590 def start_the_debugger(self, gui_adap_oid): 591 return debugger_r.start_debugger(self.rpchandler, gui_adap_oid) 592 593 def stop_the_debugger(self, idb_adap_oid): 594 "Unregister the Idb Adapter. Link objects and Idb then subject to GC" 595 self.rpchandler.unregister(idb_adap_oid) 596 597 def get_the_calltip(self, name): 598 return self.calltip.fetch_tip(name) 599 600 def get_the_completion_list(self, what, mode): 601 return self.autocomplete.fetch_completions(what, mode) 602 603 def stackviewer(self, flist_oid=None): 604 if self.user_exc_info: 605 typ, val, tb = self.user_exc_info 606 else: 607 return None 608 flist = None 609 if flist_oid is not None: 610 flist = self.rpchandler.get_remote_proxy(flist_oid) 611 while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]: 612 tb = tb.tb_next 613 sys.last_type = typ 614 sys.last_value = val 615 item = stackviewer.StackTreeItem(flist, tb) 616 return debugobj_r.remote_object_tree_item(item) 617 618 619if __name__ == '__main__': 620 from unittest import main 621 main('idlelib.idle_test.test_run', verbosity=2) 622 623capture_warnings(False) # Make sure turned off; see bpo-18081. 624