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