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

Reply via email to