Skip to content

Commit d9b553e

Browse files
SBFile::GetFile: convert SBFile back into python native files.
Summary: This makes SBFile::GetFile public and adds a SWIG typemap to convert the result back into a python native file. If the underlying File itself came from a python file, it is returned identically. Otherwise a new python file object is created using the file descriptor. Reviewers: JDevlieghere, jasonmolenda, labath Reviewed By: labath Subscribers: lldb-commits Tags: #lldb Differential Revision: https://reviews.llvm.org/D68737 llvm-svn: 374911
1 parent 1184c27 commit d9b553e

File tree

9 files changed

+200
-26
lines changed

9 files changed

+200
-26
lines changed

lldb/include/lldb/API/SBFile.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class LLDB_API SBFile {
3636
operator bool() const;
3737
bool operator!() const;
3838

39+
FileSP GetFile() const;
40+
3941
private:
4042
FileSP m_opaque_sp;
4143
};

lldb/include/lldb/Host/File.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class File : public IOObject {
6262
static mode_t ConvertOpenOptionsForPOSIXOpen(OpenOptions open_options);
6363
static llvm::Expected<OpenOptions> GetOptionsFromMode(llvm::StringRef mode);
6464
static bool DescriptorIsValid(int descriptor) { return descriptor >= 0; };
65+
static llvm::Expected<const char *>
66+
GetStreamOpenModeFromOptions(OpenOptions options);
6567

6668
File()
6769
: IOObject(eFDTypeFile), m_is_interactive(eLazyBoolCalculate),
@@ -317,6 +319,25 @@ class File : public IOObject {
317319
/// format string \a format.
318320
virtual size_t PrintfVarArg(const char *format, va_list args);
319321

322+
/// Return the OpenOptions for this file.
323+
///
324+
/// Some options like eOpenOptionDontFollowSymlinks only make
325+
/// sense when a file is being opened (or not at all)
326+
/// and may not be preserved for this method. But any valid
327+
/// File should return either or both of eOpenOptionRead and
328+
/// eOpenOptionWrite here.
329+
///
330+
/// \return
331+
/// OpenOptions flags for this file, or an error.
332+
virtual llvm::Expected<OpenOptions> GetOptions() const;
333+
334+
llvm::Expected<const char *> GetOpenMode() const {
335+
auto opts = GetOptions();
336+
if (!opts)
337+
return opts.takeError();
338+
return GetStreamOpenModeFromOptions(opts.get());
339+
}
340+
320341
/// Get the permissions for a this file.
321342
///
322343
/// \return
@@ -352,6 +373,10 @@ class File : public IOObject {
352373

353374
bool operator!() const { return !IsValid(); };
354375

376+
static char ID;
377+
virtual bool isA(const void *classID) const { return classID == &ID; }
378+
static bool classof(const File *file) { return file->isA(&ID); }
379+
355380
protected:
356381
LazyBool m_is_interactive;
357382
LazyBool m_is_real_terminal;
@@ -399,6 +424,13 @@ class NativeFile : public File {
399424
Status Flush() override;
400425
Status Sync() override;
401426
size_t PrintfVarArg(const char *format, va_list args) override;
427+
llvm::Expected<OpenOptions> GetOptions() const override;
428+
429+
static char ID;
430+
virtual bool isA(const void *classID) const override {
431+
return classID == &ID || File::isA(classID);
432+
}
433+
static bool classof(const File *file) { return file->isA(&ID); }
402434

403435
protected:
404436
bool DescriptorIsValid() const {

lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,21 @@ def test_stdout(self):
772772

773773

774774
@add_test_categories(['pyapi'])
775-
@expectedFailureAll() # FIXME implement SBFile::GetFile
775+
def test_stdout_file(self):
776+
with open(self.out_filename, 'w') as f:
777+
status = self.debugger.SetOutputFile(f)
778+
self.assertTrue(status.Success())
779+
self.handleCmd(r"script sys.stdout.write('foobar\n')")
780+
with open(self.out_filename, 'r') as f:
781+
# In python2 sys.stdout.write() returns None, which
782+
# the REPL will ignore, but in python3 it will
783+
# return the number of bytes written, which the REPL
784+
# will print out.
785+
lines = [x for x in f.read().strip().split() if x != "7"]
786+
self.assertEqual(lines, ["foobar"])
787+
788+
789+
@add_test_categories(['pyapi'])
776790
@skipIf(py_version=['<', (3,)])
777791
def test_identity(self):
778792

@@ -826,3 +840,22 @@ def test_identity(self):
826840

827841
with open(self.out_filename, 'r') as f:
828842
self.assertEqual("foobar", f.read().strip())
843+
844+
845+
@add_test_categories(['pyapi'])
846+
def test_back_and_forth(self):
847+
with open(self.out_filename, 'w') as f:
848+
# at each step here we're borrowing the file, so we have to keep
849+
# them all alive until the end.
850+
sbf = lldb.SBFile.Create(f, borrow=True)
851+
def i(sbf):
852+
for i in range(10):
853+
f = sbf.GetFile()
854+
yield f
855+
sbf = lldb.SBFile.Create(f, borrow=True)
856+
yield sbf
857+
sbf.Write(str(i).encode('ascii') + b"\n")
858+
files = list(i(sbf))
859+
with open(self.out_filename, 'r') as f:
860+
self.assertEqual(list(range(10)), list(map(int, f.read().strip().split())))
861+

lldb/scripts/Python/python-typemaps.swig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,22 @@ bool SetNumberFromPyObject<double>(double &number, PyObject *obj) {
434434
}
435435
}
436436

437+
%typemap(out) lldb::FileSP {
438+
using namespace lldb_private;
439+
$result = nullptr;
440+
lldb::FileSP &sp = $1;
441+
if (sp) {
442+
PythonFile pyfile = unwrapOrSetPythonException(PythonFile::FromFile(*sp));
443+
if (!pyfile.IsValid())
444+
return nullptr;
445+
$result = pyfile.release();
446+
}
447+
if (!$result)
448+
{
449+
$result = Py_None;
450+
Py_INCREF(Py_None);
451+
}
452+
}
437453

438454
// FIXME both of these paths wind up calling fdopen() with no provision for ever calling
439455
// fclose() on the result. SB interfaces that use FILE* should be deprecated for scripting

lldb/scripts/interface/SBFile.i

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ public:
7777
operator bool() const;
7878

7979
SBError Close();
80+
81+
%feature("docstring", "
82+
Convert this SBFile into a python io.IOBase file object.
83+
84+
If the SBFile is itself a wrapper around a python file object,
85+
this will return that original object.
86+
87+
The file returned from here should be considered borrowed,
88+
in the sense that you may read and write to it, and flush it,
89+
etc, but you should not close it. If you want to close the
90+
SBFile, call SBFile.Close().
91+
92+
If there is no underlying python file to unwrap, GetFile will
93+
use the file descriptor, if availble to create a new python
94+
file object using `open(fd, mode=..., closefd=False)`
95+
");
96+
FileSP GetFile();
8097
};
8198

8299
} // namespace lldb

lldb/source/API/SBFile.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ bool SBFile::operator!() const {
108108
return LLDB_RECORD_RESULT(!IsValid());
109109
}
110110

111+
FileSP SBFile::GetFile() const {
112+
LLDB_RECORD_METHOD_CONST_NO_ARGS(FileSP, SBFile, GetFile);
113+
return m_opaque_sp;
114+
}
115+
111116
namespace lldb_private {
112117
namespace repro {
113118

@@ -117,6 +122,7 @@ template <> void RegisterMethods<SBFile>(Registry &R) {
117122
LLDB_REGISTER_METHOD_CONST(bool, SBFile, IsValid, ());
118123
LLDB_REGISTER_METHOD_CONST(bool, SBFile, operator bool,());
119124
LLDB_REGISTER_METHOD_CONST(bool, SBFile, operator!,());
125+
LLDB_REGISTER_METHOD_CONST(FileSP, SBFile, GetFile, ());
120126
LLDB_REGISTER_METHOD(lldb::SBError, SBFile, Close, ());
121127
}
122128
} // namespace repro

lldb/source/Host/common/File.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ using namespace lldb;
3939
using namespace lldb_private;
4040
using llvm::Expected;
4141

42-
static Expected<const char *> GetStreamOpenModeFromOptions(uint32_t options) {
42+
Expected<const char *>
43+
File::GetStreamOpenModeFromOptions(File::OpenOptions options) {
4344
if (options & File::eOpenOptionAppend) {
4445
if (options & File::eOpenOptionRead) {
4546
if (options & File::eOpenOptionCanCreateNewOnly)
@@ -226,6 +227,12 @@ size_t File::PrintfVarArg(const char *format, va_list args) {
226227
return result;
227228
}
228229

230+
Expected<File::OpenOptions> File::GetOptions() const {
231+
return llvm::createStringError(
232+
llvm::inconvertibleErrorCode(),
233+
"GetOptions() not implemented for this File class");
234+
}
235+
229236
uint32_t File::GetPermissions(Status &error) const {
230237
int fd = GetDescriptor();
231238
if (!DescriptorIsValid(fd)) {
@@ -241,6 +248,8 @@ uint32_t File::GetPermissions(Status &error) const {
241248
return file_stats.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
242249
}
243250

251+
Expected<File::OpenOptions> NativeFile::GetOptions() const { return m_options; }
252+
244253
int NativeFile::GetDescriptor() const {
245254
if (DescriptorIsValid())
246255
return m_descriptor;
@@ -758,3 +767,6 @@ mode_t File::ConvertOpenOptionsForPOSIXOpen(OpenOptions open_options) {
758767

759768
return mode;
760769
}
770+
771+
char File::ID = 0;
772+
char NativeFile::ID = 0;

lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "lldb/Utility/Stream.h"
2323

2424
#include "llvm/ADT/StringSwitch.h"
25+
#include "llvm/Support/Casting.h"
2526
#include "llvm/Support/ConvertUTF.h"
2627
#include "llvm/Support/Errno.h"
2728

@@ -1012,8 +1013,6 @@ operator()(std::initializer_list<PythonObject> args) {
10121013

10131014
PythonFile::PythonFile() : PythonObject() {}
10141015

1015-
PythonFile::PythonFile(File &file, const char *mode) { Reset(file, mode); }
1016-
10171016
PythonFile::PythonFile(PyRefType type, PyObject *o) { Reset(type, o); }
10181017

10191018
PythonFile::~PythonFile() {}
@@ -1063,25 +1062,6 @@ void PythonFile::Reset(PyRefType type, PyObject *py_obj) {
10631062
PythonObject::Reset(PyRefType::Borrowed, result.get());
10641063
}
10651064

1066-
void PythonFile::Reset(File &file, const char *mode) {
1067-
if (!file.IsValid()) {
1068-
Reset();
1069-
return;
1070-
}
1071-
1072-
char *cmode = const_cast<char *>(mode);
1073-
#if PY_MAJOR_VERSION >= 3
1074-
Reset(PyRefType::Owned, PyFile_FromFd(file.GetDescriptor(), nullptr, cmode,
1075-
-1, nullptr, "ignore", nullptr, 0));
1076-
#else
1077-
// Read through the Python source, doesn't seem to modify these strings
1078-
Reset(PyRefType::Owned,
1079-
PyFile_FromFile(file.GetStream(), const_cast<char *>(""), cmode,
1080-
nullptr));
1081-
#endif
1082-
}
1083-
1084-
10851065
FileUP PythonFile::GetUnderlyingFile() const {
10861066
if (!IsValid())
10871067
return nullptr;
@@ -1238,6 +1218,13 @@ template <typename Base> class OwnedPythonFile : public Base {
12381218
return base_error;
12391219
};
12401220

1221+
PyObject *GetPythonObject() const {
1222+
assert(m_py_obj.IsValid());
1223+
return m_py_obj.get();
1224+
}
1225+
1226+
static bool classof(const File *file) = delete;
1227+
12411228
protected:
12421229
PythonFile m_py_obj;
12431230
bool m_borrowed;
@@ -1252,7 +1239,14 @@ class SimplePythonFile : public OwnedPythonFile<NativeFile> {
12521239
SimplePythonFile(const PythonFile &file, bool borrowed, int fd,
12531240
File::OpenOptions options)
12541241
: OwnedPythonFile(file, borrowed, fd, options, false) {}
1242+
1243+
static char ID;
1244+
bool isA(const void *classID) const override {
1245+
return classID == &ID || NativeFile::isA(classID);
1246+
}
1247+
static bool classof(const File *file) { return file->isA(&ID); }
12551248
};
1249+
char SimplePythonFile::ID = 0;
12561250
} // namespace
12571251

12581252
#if PY_MAJOR_VERSION >= 3
@@ -1321,7 +1315,18 @@ class PythonIOFile : public OwnedPythonFile<File> {
13211315
return Status();
13221316
}
13231317

1318+
Expected<File::OpenOptions> GetOptions() const override {
1319+
GIL takeGIL;
1320+
return GetOptionsForPyObject(m_py_obj);
1321+
}
1322+
1323+
static char ID;
1324+
bool isA(const void *classID) const override {
1325+
return classID == &ID || File::isA(classID);
1326+
}
1327+
static bool classof(const File *file) { return file->isA(&ID); }
13241328
};
1329+
char PythonIOFile::ID = 0;
13251330
} // namespace
13261331

13271332
namespace {
@@ -1542,4 +1547,42 @@ PythonFile::ConvertToFileForcingUseOfScriptingIOMethods(bool borrowed) {
15421547
#endif
15431548
}
15441549

1550+
Expected<PythonFile> PythonFile::FromFile(File &file, const char *mode) {
1551+
if (!file.IsValid())
1552+
return llvm::createStringError(llvm::inconvertibleErrorCode(),
1553+
"invalid file");
1554+
1555+
if (auto *simple = llvm::dyn_cast<SimplePythonFile>(&file))
1556+
return Retain<PythonFile>(simple->GetPythonObject());
1557+
#if PY_MAJOR_VERSION >= 3
1558+
if (auto *pythonio = llvm::dyn_cast<PythonIOFile>(&file))
1559+
return Retain<PythonFile>(pythonio->GetPythonObject());
1560+
#endif
1561+
1562+
if (!mode) {
1563+
auto m = file.GetOpenMode();
1564+
if (!m)
1565+
return m.takeError();
1566+
mode = m.get();
1567+
}
1568+
1569+
PyObject *file_obj;
1570+
#if PY_MAJOR_VERSION >= 3
1571+
file_obj = PyFile_FromFd(file.GetDescriptor(), nullptr, mode, -1, nullptr,
1572+
"ignore", nullptr, 0);
1573+
#else
1574+
// Read through the Python source, doesn't seem to modify these strings
1575+
char *cmode = const_cast<char *>(mode);
1576+
// We pass ::flush instead of ::fclose here so we borrow the FILE* --
1577+
// the lldb_private::File still owns it.
1578+
file_obj =
1579+
PyFile_FromFile(file.GetStream(), const_cast<char *>(""), cmode, ::fflush);
1580+
#endif
1581+
1582+
if (!file_obj)
1583+
return exception();
1584+
1585+
return Take<PythonFile>(file_obj);
1586+
}
1587+
15451588
#endif

lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ class PythonCallable : public PythonObject {
638638
void Reset(PyRefType type, PyObject *py_obj) override;
639639

640640
ArgInfo GetNumArguments() const;
641-
641+
642642
// If the callable is a Py_Class, then find the number of arguments
643643
// of the __init__ method.
644644
ArgInfo GetNumInitArguments() const;
@@ -658,7 +658,6 @@ class PythonCallable : public PythonObject {
658658
class PythonFile : public PythonObject {
659659
public:
660660
PythonFile();
661-
PythonFile(File &file, const char *mode);
662661
PythonFile(PyRefType type, PyObject *o);
663662

664663
~PythonFile() override;
@@ -668,7 +667,21 @@ class PythonFile : public PythonObject {
668667
using PythonObject::Reset;
669668

670669
void Reset(PyRefType type, PyObject *py_obj) override;
671-
void Reset(File &file, const char *mode);
670+
671+
static llvm::Expected<PythonFile> FromFile(File &file,
672+
const char *mode = nullptr);
673+
674+
// FIXME delete this after FILE* typemaps are deleted
675+
// and ScriptInterpreterPython is fixed
676+
PythonFile(File &file, const char *mode = nullptr) {
677+
auto f = FromFile(file, mode);
678+
if (f)
679+
*this = std::move(f.get());
680+
else {
681+
Reset();
682+
llvm::consumeError(f.takeError());
683+
}
684+
}
672685

673686
lldb::FileUP GetUnderlyingFile() const;
674687

0 commit comments

Comments
 (0)