llvm-project/lldb/source/Interpreter/embedded_interpreter.py
Jonas Devlieghere 6cde6ac03c
[lldb] Don't overwrite quit and exit builtins in the Python interpreter
The interactive interpreter is overwriting the exit and quit builtins
with an instance of LLDBQuitter in order to make exit and quit behave
like exit() and quit(). It does that by overwriting the __repr__
function to call itself.

Despite being a neat trick, it has the unintentional side effect that
printing these builtins now quits the interpreter:

  (lldb) script
  Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.
  >>> print(exit)
  (lldb)

You might consider the above example slightly convoluted, but a more
realistic situation is calling locals():

  (lldb) script
  Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.
  >>> locals()
  (lldb)

This patch keeps the existing behavior but without overwriting the
builtins. Instead, it looks for quit and exit in the input. If they're
present, we exit the interpreter with the help of an exception.

The previous implementation also used globals to differentiate between
exit getting called from the interactive interpreter or from inside a
script. This patch achieves the same by using a different exception in
for the interpreter case.

rdar://84095490

Differential revision: https://reviews.llvm.org/D127895
2022-06-15 14:53:40 -07:00

127 lines
3.7 KiB
Python

import sys
if sys.version_info[0] < 3:
import __builtin__ as builtins
else:
import builtins
import code
import lldb
import traceback
try:
import readline
import rlcompleter
except ImportError:
have_readline = False
except AttributeError:
# This exception gets hit by the rlcompleter when Linux is using
# the readline suppression import.
have_readline = False
else:
have_readline = True
if 'libedit' in readline.__doc__:
readline.parse_and_bind('bind ^I rl_complete')
else:
readline.parse_and_bind('tab: complete')
# When running one line, we might place the string to run in this string
# in case it would be hard to correctly escape a string's contents
g_run_one_line_str = None
def get_terminal_size(fd):
try:
import fcntl
import termios
import struct
hw = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
except:
hw = (0, 0)
return hw
class LLDBExit(SystemExit):
pass
def strip_and_check_exit(line):
line = line.rstrip()
if line in ('exit', 'quit'):
raise LLDBExit
return line
def readfunc(prompt):
line = input(prompt)
return strip_and_check_exit(line)
def readfunc_stdio(prompt):
sys.stdout.write(prompt)
sys.stdout.flush()
line = sys.stdin.readline()
# Readline always includes a trailing newline character unless the file
# ends with an incomplete line. An empty line indicates EOF.
if not line:
raise EOFError
return strip_and_check_exit(line)
def run_python_interpreter(local_dict):
# Pass in the dictionary, for continuity from one session to the next.
try:
fd = sys.stdin.fileno()
interacted = False
if get_terminal_size(fd)[1] == 0:
try:
import termios
old = termios.tcgetattr(fd)
if old[3] & termios.ECHO:
# Need to turn off echoing and restore
new = termios.tcgetattr(fd)
new[3] = new[3] & ~termios.ECHO
try:
termios.tcsetattr(fd, termios.TCSADRAIN, new)
interacted = True
code.interact(
banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()'.",
readfunc=readfunc_stdio,
local=local_dict)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
except:
pass
# Don't need to turn off echoing
if not interacted:
code.interact(
banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.",
readfunc=readfunc_stdio,
local=local_dict)
else:
# We have a real interactive terminal
code.interact(
banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.",
readfunc=readfunc,
local=local_dict)
except LLDBExit:
pass
except SystemExit as e:
if e.code:
print('Script exited with code %s' % e.code)
def run_one_line(local_dict, input_string):
global g_run_one_line_str
try:
input_string = strip_and_check_exit(input_string)
repl = code.InteractiveConsole(local_dict)
if input_string:
repl.runsource(input_string)
elif g_run_one_line_str:
repl.runsource(g_run_one_line_str)
except LLDBExit:
pass
except SystemExit as e:
if e.code:
print('Script exited with code %s' % e.code)