JDevlieghere created this revision. JDevlieghere added reviewers: labath, mib. Herald added a project: All. JDevlieghere requested review of this revision.
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 and raises an appropriate exception. The previous implementation also used globals to differentiate between exit getting called from the interactive interpreter and from scripts. This patch makes use of a custom exception in order to do the same. rdar://84095490 https://reviews.llvm.org/D127895 Files: lldb/source/Interpreter/embedded_interpreter.py lldb/test/Shell/ScriptInterpreter/Python/exit.test
Index: lldb/test/Shell/ScriptInterpreter/Python/exit.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Python/exit.test @@ -0,0 +1,27 @@ +# RUN: %lldb -o 'script quit' | FileCheck %s --check-prefix SILENT +# RUN: %lldb -o 'script quit()' | FileCheck %s --check-prefix SILENT + +# RUN: %lldb -o 'script exit' | FileCheck %s --check-prefix SILENT +# RUN: %lldb -o 'script exit()' | FileCheck %s --check-prefix SILENT + +# RUN: echo -e 'script\nquit' > %t +# RUN: cat %t | %lldb | FileCheck %s --check-prefix SILENT + +# RUN: echo -e 'script\nexit' > %t +# RUN: cat %t | %lldb | FileCheck %s --check-prefix SILENT + +# SILENT-NOT: Script exited with code + +# RUN: %lldb -o 'script quit(100+23)' | FileCheck %s --check-prefix VERBOSE +# RUN: %lldb -o 'script exit(100+23)' | FileCheck %s --check-prefix VERBOSE + +# RUN: echo -e 'script\nexit(100+23)' > %t +# RUN: cat %t | %lldb | FileCheck %s --check-prefix VERBOSE + +# RUN: echo -e 'script\nquit(100+23)' > %t +# RUN: cat %t | %lldb | FileCheck %s --check-prefix VERBOSE + +# VERBOSE: Script exited with code 123 + +# RUN: %lldb -o 'script print(locals())' | FileCheck %s --check-prefix LOCALS +# LOCALS: __builtins__ Index: lldb/source/Interpreter/embedded_interpreter.py =================================================================== --- lldb/source/Interpreter/embedded_interpreter.py +++ lldb/source/Interpreter/embedded_interpreter.py @@ -1,4 +1,4 @@ -import sys +import sys if sys.version_info[0] < 3: import __builtin__ as builtins else: @@ -23,36 +23,6 @@ else: readline.parse_and_bind('tab: complete') -g_builtin_override_called = False - - -class LLDBQuitter(object): - - def __init__(self, name): - self.name = name - - def __repr__(self): - self() - - def __call__(self, code=None): - global g_builtin_override_called - g_builtin_override_called = True - raise SystemExit(-1) - - -def setquit(): - '''Redefine builtin functions 'quit()' and 'exit()' to print a message and raise an EOFError exception.''' - # This function will be called prior to each interactive - # interpreter loop or each single line, so we set the global - # g_builtin_override_called to False so we know if a SystemExit - # is thrown, we can catch it and tell the difference between - # a call to "quit()" or "exit()" and something like - # "sys.exit(123)" - global g_builtin_override_called - g_builtin_override_called = False - builtins.quit = LLDBQuitter('quit') - builtins.exit = LLDBQuitter('exit') - # 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 @@ -70,6 +40,24 @@ return hw +class LLDBExit(SystemExit): + pass + + +def strip_and_check_exit(line): + line = line.rstrip() + if line == 'exit': + raise LLDBExit + if line == '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() @@ -78,12 +66,11 @@ # ends with an incomplete line. An empty line indicates EOF. if not line: raise EOFError - return line.rstrip() + return strip_and_check_exit(line) def run_python_interpreter(local_dict): # Pass in the dictionary, for continuity from one session to the next. - setquit() try: fd = sys.stdin.fileno() interacted = False @@ -116,24 +103,26 @@ # 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: - global g_builtin_override_called - if not g_builtin_override_called: - print('Script exited with %s' % (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 - setquit() 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: - global g_builtin_override_called - if not g_builtin_override_called: - print('Script exited with %s' % (e)) + if e.code: + print('Script exited with code %s' % e.code)
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits