Commit Lawrence D'Anna's patch to change
authorJason Molenda <jmolenda@apple.com>
Thu, 2 Nov 2017 02:43:27 +0000 (02:43 +0000)
committerJason Molenda <jmolenda@apple.com>
Thu, 2 Nov 2017 02:43:27 +0000 (02:43 +0000)
SetOututFileHandle to work with IOBase.

I did make one change after checking with Larry --
I renamed SBDebugger::Flush to FlushDebuggerOutputHandles
and added a short docstring to the .i file to make it
a little clearer under which context programs may need
to use this API.

Differential Revision: https://reviews.llvm.org/D39128
<rdar://problem/34870417>

llvm-svn: 317182

12 files changed:
lldb/include/lldb/API/SBDebugger.h
lldb/include/lldb/Core/Debugger.h
lldb/include/lldb/Host/File.h
lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py [new file with mode: 0644]
lldb/scripts/interface/SBDebugger.i
lldb/source/API/SBDebugger.cpp
lldb/source/Core/Debugger.cpp
lldb/source/Host/common/File.cpp
lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h
lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h

index 8379a69..7a74fd1 100644 (file)
@@ -78,6 +78,8 @@ public:
   void SetOutputFileHandle(FILE *f, bool transfer_ownership);
 
   void SetErrorFileHandle(FILE *f, bool transfer_ownership);
+  
+  void FlushDebuggerOutputHandles();
 
   FILE *GetInputFileHandle();
 
index 34d35ff..75c389f 100644 (file)
@@ -139,6 +139,8 @@ public:
   void SetOutputFileHandle(FILE *fh, bool tranfer_ownership);
 
   void SetErrorFileHandle(FILE *fh, bool tranfer_ownership);
+                     
+  void FlushDebuggerOutputHandles();
 
   void SaveInputTerminalState();
 
index 1dfa12e..5ebd3b7 100644 (file)
@@ -62,6 +62,17 @@ public:
         m_is_interactive(eLazyBoolCalculate),
         m_is_real_terminal(eLazyBoolCalculate) {}
 
+  File(File &&rhs);
+
+  File& operator= (File &&rhs);
+
+  void Swap(File &other);
+
+  File(void *cookie,
+       int (*readfn)(void *, char *, int),
+       int (*writefn)(void *, const char *, int),
+       int (*closefn)(void *));
+
   //------------------------------------------------------------------
   /// Constructor with path.
   ///
@@ -479,9 +490,6 @@ protected:
   LazyBool m_is_interactive;
   LazyBool m_is_real_terminal;
   LazyBool m_supports_colors;
-
-private:
-  DISALLOW_COPY_AND_ASSIGN(File);
 };
 
 } // namespace lldb_private
diff --git a/lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py b/lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py
new file mode 100644 (file)
index 0000000..5ddd633
--- /dev/null
@@ -0,0 +1,228 @@
+"""
+Test lldb Python API for setting output and error file handles 
+"""
+
+from __future__ import print_function
+
+
+import contextlib
+import os
+import io
+import re
+import platform
+import unittest
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class StringIO(io.TextIOBase):
+
+    def __init__(self, buf=''):
+        self.buf = buf
+
+    def writable(self):
+        return True
+
+    def write(self, s):
+        self.buf += s
+        return len(s)
+
+
+class BadIO(io.TextIOBase):
+
+    def writable(self):
+        return True
+
+    def write(self, s):
+        raise Exception('OH NOE')
+    
+
+@contextlib.contextmanager
+def replace_stdout(new):
+    old = sys.stdout
+    sys.stdout = new
+    try:
+        yield
+    finally:
+        sys.stdout = old
+
+
+def handle_command(debugger, cmd, raise_on_fail=True, collect_result=True):
+
+    ret = lldb.SBCommandReturnObject()
+
+    if collect_result:
+        interpreter = debugger.GetCommandInterpreter()    
+        interpreter.HandleCommand(cmd, ret)
+    else:
+        debugger.HandleCommand(cmd)
+        
+    if hasattr(debugger, 'FlushDebuggerOutputHandles'):
+        debugger.FlushDebuggerOutputHandles()
+        
+    if collect_result and raise_on_fail and not ret.Succeeded():
+        raise Exception
+    
+    return ret.GetOutput()
+
+
+        
+class FileHandleTestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    def comment(self, *args):
+        if self.session is not None:
+            print(*args, file=self.session)
+
+    def skip_windows(self):
+        if platform.system() == 'Windows':
+            self.skipTest('windows')
+        
+
+    @add_test_categories(['pyapi'])
+    @no_debug_info_test
+    def test_file_out(self):
+
+        debugger = lldb.SBDebugger.Create()
+        try:
+            with open('output', 'w') as f:
+                debugger.SetOutputFileHandle(f, False)
+                handle_command(debugger, 'script print("foobar")')
+
+            with open('output', 'r') as f:
+                self.assertEqual(f.read().strip(), "foobar")
+
+        finally:
+            self.RemoveTempFile('output')
+            lldb.SBDebugger.Destroy(debugger)
+            
+
+    @add_test_categories(['pyapi'])
+    @no_debug_info_test
+    def test_file_error(self):
+
+        debugger = lldb.SBDebugger.Create()
+        try:
+            with open('output', 'w') as f:
+                debugger.SetErrorFileHandle(f, False)
+                handle_command(debugger, 'lolwut', raise_on_fail=False, collect_result=False)
+                
+            with open('output', 'r') as f:
+                errors = f.read()
+                self.assertTrue(re.search(r'error:.*lolwut', errors))
+
+        finally:
+            self.RemoveTempFile('output')
+            lldb.SBDebugger.Destroy(debugger)
+            
+
+    @add_test_categories(['pyapi'])
+    @no_debug_info_test
+    def test_string_out(self):
+
+        self.skip_windows()
+
+        io = StringIO()
+        debugger = lldb.SBDebugger.Create()
+        try:
+            debugger.SetOutputFileHandle(io, False)
+            handle_command(debugger, 'script print("foobar")')
+
+            self.assertEqual(io.buf.strip(), "foobar")
+            
+        finally:
+            lldb.SBDebugger.Destroy(debugger)
+                    
+
+    @add_test_categories(['pyapi'])
+    @no_debug_info_test
+    def test_string_error(self):
+
+        self.skip_windows()
+
+        io = StringIO()
+        debugger = lldb.SBDebugger.Create()
+        try:
+            debugger.SetErrorFileHandle(io, False)
+            handle_command(debugger, 'lolwut', raise_on_fail=False, collect_result=False)
+
+            errors = io.buf
+            self.assertTrue(re.search(r'error:.*lolwut', errors))
+
+        finally:
+            lldb.SBDebugger.Destroy(debugger)
+
+    @add_test_categories(['pyapi'])
+    @no_debug_info_test
+    def test_replace_stdout(self):
+
+        self.skip_windows()
+
+        io = StringIO()
+        debugger = lldb.SBDebugger.Create()
+        try:
+
+            with replace_stdout(io):
+                handle_command(debugger, 'script print("lol, crash")', collect_result=False)
+
+        finally:
+            lldb.SBDebugger.Destroy(debugger)
+
+
+    @add_test_categories(['pyapi'])
+    @no_debug_info_test
+    def test_replace_stdout_with_nonfile(self):
+
+        self.skip_windows()
+
+        io = StringIO()
+
+        with replace_stdout(io):
+
+            class Nothing():
+                pass
+
+            debugger = lldb.SBDebugger.Create()
+            try:
+                with replace_stdout(Nothing):
+                    self.assertEqual(sys.stdout, Nothing)
+                    handle_command(debugger, 'script print("lol, crash")', collect_result=False)
+                    self.assertEqual(sys.stdout, Nothing)
+            finally:
+                lldb.SBDebugger.Destroy(debugger)
+
+            sys.stdout.write("FOO")
+
+        self.assertEqual(io.buf, "FOO")
+
+
+    @add_test_categories(['pyapi'])
+    @no_debug_info_test
+    def test_stream_error(self):
+
+        self.skip_windows()
+
+        messages = list()
+
+        io = BadIO()
+        debugger = lldb.SBDebugger.Create()
+        try:
+            debugger.SetOutputFileHandle(io, False)
+            debugger.SetLoggingCallback(messages.append)
+            handle_command(debugger, 'log enable lldb script')
+            handle_command(debugger, 'script print "foobar"')
+            
+        finally:
+            lldb.SBDebugger.Destroy(debugger)
+
+        for message in messages:
+            self.comment("GOT: " + message.strip())
+
+        self.assertTrue(any('OH NOE' in msg for msg in messages))
+        self.assertTrue(any('BadIO' in msg for msg in messages))
+
+
index 9f746d3..fbb15f4 100644 (file)
@@ -171,6 +171,14 @@ public:
     void
     SetErrorFileHandle (FILE *f, bool transfer_ownership);
 
+    %feature("docstring",
+    "Flush the Debugger's Output/Error file handles.
+For instance, this is needed by a repl implementation on top of 
+the SB API, where fine grained control of output timing was needed."
+    ) FlushDebuggerOutputHandles;
+    void
+    FlushDebuggerOutputHandles ();
+
     FILE *
     GetInputFileHandle ();
 
index d3294da..af883ac 100644 (file)
@@ -270,6 +270,18 @@ void SBDebugger::SetInputFileHandle(FILE *fh, bool transfer_ownership) {
     m_opaque_sp->SetInputFileHandle(fh, transfer_ownership);
 }
 
+void SBDebugger::FlushDebuggerOutputHandles() {
+  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_API));
+  
+  if (log)
+    log->Printf(
+                "SBDebugger(%p)::FlushDebuggerOutputHandles ()",
+                static_cast<void *>(m_opaque_sp.get()));
+
+  if (m_opaque_sp)
+    m_opaque_sp->FlushDebuggerOutputHandles();
+}
+
 void SBDebugger::SetOutputFileHandle(FILE *fh, bool transfer_ownership) {
   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_API));
 
index a4d7815..500913a 100644 (file)
@@ -680,6 +680,15 @@ void Debugger::Destroy(DebuggerSP &debugger_sp) {
   if (!debugger_sp)
     return;
 
+  /*
+   * FILE* get flushed on process exit.  If those FILEs need to call into python
+   * to flush, we can't have them flushing after python is already torn down.
+   * That would result in a segfault.  We are still relying on the python script
+   * to tear down the debugger before it exits.
+   */
+  debugger_sp->m_output_file_sp->Flush();
+  debugger_sp->m_error_file_sp->Flush();
+
   debugger_sp->Clear();
 
   if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) {
@@ -896,6 +905,11 @@ void Debugger::SetErrorFileHandle(FILE *fh, bool tranfer_ownership) {
     err_file.SetStream(stderr, false);
 }
 
+void Debugger::FlushDebuggerOutputHandles() {
+  m_output_file_sp->Flush();
+  m_error_file_sp->Flush();
+}
+
 void Debugger::SaveInputTerminalState() {
   if (m_input_file_sp) {
     File &in_file = m_input_file_sp->GetFile();
index 6ee4e89..ec5c435 100644 (file)
@@ -14,6 +14,7 @@
 #include <limits.h>
 #include <stdarg.h>
 #include <stdio.h>
+#include <assert.h>
 
 #ifdef _WIN32
 #include "lldb/Host/windows/windows.h"
@@ -71,6 +72,129 @@ static const char *GetStreamOpenModeFromOptions(uint32_t options) {
 int File::kInvalidDescriptor = -1;
 FILE *File::kInvalidStream = NULL;
 
+File::File(File &&rhs)
+  : IOObject(eFDTypeFile, false), m_descriptor(kInvalidDescriptor),
+  m_stream(kInvalidStream), m_options(), m_own_stream(false),
+  m_is_interactive(eLazyBoolCalculate),
+  m_is_real_terminal(eLazyBoolCalculate),
+  m_supports_colors(eLazyBoolCalculate)
+{
+  Swap(rhs);
+}
+
+File& File::operator= (File &&rhs)
+{
+  Close();
+  Swap(rhs);
+  return *this;
+}
+
+void File::Swap(File &rhs)
+{
+    std::swap(m_descriptor, rhs.m_descriptor);
+    std::swap(m_stream, rhs.m_stream);
+    std::swap(m_own_stream, rhs.m_own_stream);
+    std::swap(m_options, rhs.m_options);
+    std::swap(m_is_interactive, rhs.m_is_interactive);
+    std::swap(m_is_real_terminal, rhs.m_is_real_terminal);
+    std::swap(m_supports_colors, rhs.m_supports_colors);
+}
+
+#if defined(__linux__)
+
+struct context {
+  void *cookie;
+  int (*readfn)(void *, char *, int);
+  int (*writefn)(void *, const char *, int);
+  int (*closefn)(void *);
+};
+
+static ssize_t
+write_wrapper(void *c, const char *buf, size_t size)
+{
+  auto ctx = (struct context *)c;
+  if (size > INT_MAX) {
+    size = INT_MAX;
+  }
+  ssize_t wrote = ctx->writefn(ctx->cookie, buf, (int)size);
+  assert(wrote < 0 || (size_t)wrote <= size);
+  if (wrote < 0) {
+    return -1;
+  } else {
+    return (int)wrote;
+  }
+}
+
+static ssize_t
+read_wrapper(void *c, char *buf, size_t size)
+{
+  auto ctx = (struct context *)c;
+  if (size > INT_MAX) {
+    size = INT_MAX;
+  }
+  ssize_t read = ctx->writefn(ctx->cookie, buf, (int)size);
+  assert(read < 0 || (size_t)read <= size);
+  if (read < 0) {
+    return -1;
+  } else {
+    return (int)read;
+  }
+}
+
+static int
+close_wrapper(void *c)
+{
+  auto ctx = (struct context *)c;
+  int ret = ctx->closefn(ctx->cookie);
+  delete ctx;
+  return ret;
+}
+
+#endif
+
+File::File(void *cookie,
+           int (*readfn)(void *, char *, int),
+           int (*writefn)(void *, const char *, int),
+           int (*closefn)(void *))
+    : IOObject(eFDTypeFile, false), m_descriptor(kInvalidDescriptor),
+    m_stream(kInvalidStream), m_options(), m_own_stream(false),
+    m_is_interactive(eLazyBoolCalculate),
+    m_is_real_terminal(eLazyBoolCalculate),
+    m_supports_colors(eLazyBoolCalculate)
+{
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__)
+    m_stream = funopen(cookie, readfn, writefn, NULL, closefn);
+#elif defined(__linux__)
+    cookie_io_functions_t io_funcs = {};
+    io_funcs.read = read_wrapper;
+    io_funcs.write = write_wrapper;
+    io_funcs.close = close_wrapper;
+    const char *mode = NULL;
+    if (readfn && writefn) {
+      mode = "r+";
+    } else if (readfn) {
+      mode = "r";
+    } else if (writefn) {
+      mode = "w";
+    }
+    if (mode) {
+      struct context *ctx = new context;
+      ctx->readfn = readfn;
+      ctx->writefn = writefn;
+      ctx->closefn = closefn;
+      ctx->cookie = cookie;
+      m_stream = fopencookie(ctx, mode, io_funcs);
+      if (!m_stream) {
+        delete ctx;
+      }
+    }
+#endif
+    if (m_stream) {
+        m_own_stream = true;
+    }
+}
+
+
 File::File(const char *path, uint32_t options, uint32_t permissions)
     : IOObject(eFDTypeFile, false), m_descriptor(kInvalidDescriptor),
       m_stream(kInvalidStream), m_options(), m_own_stream(false),
index 966bdff..135c198 100644 (file)
@@ -17,6 +17,7 @@
 #include "PythonDataObjects.h"
 #include "ScriptInterpreterPython.h"
 
+#include "lldb/Utility/Log.h"
 #include "lldb/Host/File.h"
 #include "lldb/Host/FileSystem.h"
 #include "lldb/Interpreter/ScriptInterpreter.h"
@@ -959,8 +960,10 @@ PythonFile::~PythonFile() {}
 
 bool PythonFile::Check(PyObject *py_obj) {
 #if PY_MAJOR_VERSION < 3
-  return PyFile_Check(py_obj);
-#else
+  if (PyFile_Check(py_obj)) {
+    return true;
+  }
+#endif
   // In Python 3, there is no `PyFile_Check`, and in fact PyFile is not even a
   // first-class object type anymore.  `PyFile_FromFd` is just a thin wrapper
   // over `io.open()`, which returns some object derived from `io.IOBase`.
@@ -977,11 +980,11 @@ bool PythonFile::Check(PyObject *py_obj) {
 
   if (1 != PyObject_IsSubclass(object_type.get(), io_base_class.get()))
     return false;
+
   if (!object_type.HasAttribute("fileno"))
     return false;
 
   return true;
-#endif
 }
 
 void PythonFile::Reset(PyRefType type, PyObject *py_obj) {
@@ -989,7 +992,7 @@ void PythonFile::Reset(PyRefType type, PyObject *py_obj) {
   // `py_obj` it still gets decremented if necessary.
   PythonObject result(type, py_obj);
 
-  if (!PythonFile::Check(py_obj)) {
+  if (py_obj == NULL || !PythonFile::Check(py_obj)) {
     PythonObject::Reset();
     return;
   }
@@ -1034,17 +1037,168 @@ uint32_t PythonFile::GetOptionsFromMode(llvm::StringRef mode) {
       .Default(0);
 }
 
+static const char *
+str(PyObject *o, const char *defaultt, PyObject **cleanup)
+{
+  *cleanup = NULL;
+  if (o == NULL) {
+    return defaultt;
+  }
+  PyObject *string = PyObject_Str(o);
+  *cleanup = string;
+  if (string == NULL) {
+    return defaultt;
+  }
+  if (PyUnicode_Check(string)) {
+    PyObject *bytes = PyUnicode_AsEncodedString(string, "utf-8", "Error ~");
+    if (bytes == NULL) {
+      return defaultt;
+    }
+    Py_XDECREF(string);
+    *cleanup = bytes;
+    return PyBytes_AS_STRING(bytes);
+  } else {
+    return PyBytes_AS_STRING(string);
+  }
+}
+
+
+static void
+log_exception(const char *fmt, PyObject *obj)
+{
+  Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_SCRIPT);
+  if (!log) {
+    return;
+  }
+  PyObject *pyclass = PyObject_Type(obj);
+  PyObject *pyclassname = NULL;
+  if (pyclass) {
+    pyclassname = PyObject_GetAttrString(pyclass, "__name__");
+  }
+  PyObject *exception = NULL, *v = NULL, *tb = NULL;
+  PyErr_Fetch(&exception, &v, &tb);
+  if (exception) {
+    PyErr_NormalizeException(&exception, &v, &tb);
+  }
+  PyObject *cleanup1 = NULL, *cleanup2 = NULL;
+  log->Printf(fmt,
+              str(pyclassname, "UknownClass", &cleanup1),
+              str(v, "unknown error", &cleanup2));
+  Py_XDECREF(cleanup1);
+  Py_XDECREF(cleanup2);
+  Py_XDECREF(pyclass);
+  Py_XDECREF(pyclassname);
+  Py_XDECREF(exception);
+  Py_XDECREF(v);
+  Py_XDECREF(tb);
+
+}
+
+class GIL
+{
+private:
+  PyGILState_STATE m_state;
+public:
+  GIL() {
+    m_state = PyGILState_Ensure();
+  }
+  ~GIL() {
+    PyGILState_Release(m_state);
+  }
+};
+
+#define callmethod(obj, name, fmt, ...) \
+  PythonObject(PyRefType::Owned, PyObject_CallMethod(obj, (char*)name, (char*)fmt, __VA_ARGS__))
+
+#define callmethod0(obj, name, fmt) \
+  PythonObject(PyRefType::Owned, PyObject_CallMethod(obj, (char*)name, (char*)fmt))
+
+static int readfn(void *ctx, char *buffer, int n)
+{
+  GIL gil;
+  auto *file = (PyObject *) ctx;
+
+  PythonObject pyresult = callmethod(file, "read", "(i)", n);
+  if (!pyresult.IsValid() || PyErr_Occurred()) {
+    log_exception("read from python %s failed: %s", file);
+    return -1;
+  }
+  if (!PyBytes_Check(pyresult)) {
+    PyErr_SetString(PyExc_TypeError, "read didn't return bytes");
+    log_exception("read from python %s failed: %s", file);
+    return -1;
+  }
+
+  int r = 0;
+  char *data = NULL;
+  ssize_t length = 0;
+  r = PyBytes_AsStringAndSize(pyresult, &data, &length);
+  if (r || length < 0 || PyErr_Occurred()) {
+    log_exception("read from python %s failed: %s", file);
+    return -1;
+  }
+
+  memcpy(buffer, data, length);
+  return length;
+}
+
+static int writefn(void *ctx, const char *buffer, int n)
+{
+  GIL gil;
+  auto *file = (PyObject *) ctx;
+
+  PythonObject pyresult = callmethod(file, "write", "(s#)", buffer, n);
+  if (!pyresult.IsValid() || PyErr_Occurred()) {
+    log_exception("write to python %s failed: %s", file);
+    return -1;
+  }
+
+  int result = PyLong_AsLong(pyresult);
+  if (PyErr_Occurred()) {
+    log_exception("read from python %s failed: %s", file);
+    return -1;
+  }
+
+  return result;
+}
+
+static int closefn(void *ctx) {
+  GIL gil;
+  auto *file = (PyObject *) ctx;
+  Py_XDECREF(file);
+  return 0;
+}
+
 bool PythonFile::GetUnderlyingFile(File &file) const {
   if (!IsValid())
     return false;
 
+  if (!PythonFile::Check(m_py_obj))
+    return false;
+  
   file.Close();
-  // We don't own the file descriptor returned by this function, make sure the
-  // File object knows about that.
-  file.SetDescriptor(PyObject_AsFileDescriptor(m_py_obj), false);
-  PythonString py_mode = GetAttributeValue("mode").AsType<PythonString>();
-  file.SetOptions(PythonFile::GetOptionsFromMode(py_mode.GetString()));
-  return file.IsValid();
+  
+  int fd = PyObject_AsFileDescriptor(m_py_obj);
+  if (fd >= 0) {
+    // We don't own the file descriptor returned by this function, make sure the
+    // File object knows about that.
+    file.SetDescriptor(PyObject_AsFileDescriptor(m_py_obj), false);
+    PythonString py_mode = GetAttributeValue("mode").AsType<PythonString>();
+    file.SetOptions(PythonFile::GetOptionsFromMode(py_mode.GetString()));
+    return file.IsValid();
+  }
+  
+  bool readable = PyObject_IsTrue(callmethod0(m_py_obj, "readable", "()"));
+  bool writable = PyObject_IsTrue(callmethod0(m_py_obj, "writable", "()"));
+  
+  Py_XINCREF(m_py_obj);
+  file = File(m_py_obj, readable ? readfn : NULL, writable ? writefn : NULL, closefn);
+  if (!file.IsValid()) {
+    closefn(m_py_obj);
+    return false;
+  } else {
+    return true;
+  }
 }
 
 #endif
index beeb647..60e1337 100644 (file)
@@ -113,6 +113,8 @@ public:
       Reset(PyRefType::Borrowed, rhs.m_py_obj);
   }
 
+  operator PyObject*() const { return m_py_obj; }
+
   // PythonObject is implicitly convertible to PyObject *, which will call the
   // wrong overload.  We want to explicitly disallow this, since a PyObject
   // *always* owns its reference.  Therefore the overload which takes a
index 6c39690..246da4f 100644 (file)
@@ -504,7 +504,7 @@ void ScriptInterpreterPython::LeaveSession() {
 }
 
 bool ScriptInterpreterPython::SetStdHandle(File &file, const char *py_name,
-                                           PythonFile &save_file,
+                                           PythonObject &save_file,
                                            const char *mode) {
   if (file.IsValid()) {
     // Flush the file before giving it to python to avoid interleaved output.
@@ -512,8 +512,7 @@ bool ScriptInterpreterPython::SetStdHandle(File &file, const char *py_name,
 
     PythonDictionary &sys_module_dict = GetSysModuleDictionary();
 
-    save_file = sys_module_dict.GetItemForKey(PythonString(py_name))
-                    .AsType<PythonFile>();
+    save_file = sys_module_dict.GetItemForKey(PythonString(py_name));
 
     PythonFile new_file(file, mode);
     sys_module_dict.SetItemForKey(PythonString(py_name), new_file);
index a71fcea..ed200e4 100644 (file)
@@ -541,12 +541,12 @@ protected:
 
   bool GetEmbeddedInterpreterModuleObjects();
 
-  bool SetStdHandle(File &file, const char *py_name, PythonFile &save_file,
+  bool SetStdHandle(File &file, const char *py_name, PythonObject &save_file,
                     const char *mode);
 
-  PythonFile m_saved_stdin;
-  PythonFile m_saved_stdout;
-  PythonFile m_saved_stderr;
+  PythonObject m_saved_stdin;
+  PythonObject m_saved_stdout;
+  PythonObject m_saved_stderr;
   PythonObject m_main_module;
   PythonObject m_lldb_module;
   PythonDictionary m_session_dict;