#include "lldb/API/SBAttachInfo.h"
#include "lldb/API/SBBlock.h"
#include "lldb/API/SBBreakpoint.h"
-#include "lldb/API/SBBreakpointName.h"
#include "lldb/API/SBBreakpointLocation.h"
+#include "lldb/API/SBBreakpointName.h"
#include "lldb/API/SBBroadcaster.h"
#include "lldb/API/SBCommandInterpreter.h"
#include "lldb/API/SBCommandReturnObject.h"
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBExecutionContext.h"
#include "lldb/API/SBExpressionOptions.h"
+#include "lldb/API/SBFile.h"
#include "lldb/API/SBFileSpec.h"
#include "lldb/API/SBFileSpecList.h"
#include "lldb/API/SBFrame.h"
class LLDB_API SBEventList;
class LLDB_API SBExecutionContext;
class LLDB_API SBExpressionOptions;
+class LLDB_API SBFile;
class LLDB_API SBFileSpec;
class LLDB_API SBFileSpecList;
class LLDB_API SBFrame;
friend class SBTrace;
friend class SBValue;
friend class SBWatchpoint;
+ friend class SBFile;
lldb_private::Status *get();
--- /dev/null
+//===-- SBFile.h --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SBFile_h_
+#define LLDB_SBFile_h_
+
+#include "lldb/API/SBDefines.h"
+
+namespace lldb {
+
+class LLDB_API SBFile {
+public:
+ SBFile();
+ SBFile(FILE *file, bool transfer_ownership);
+ SBFile(int fd, const char *mode, bool transfer_ownership);
+ ~SBFile();
+
+ SBError Read(uint8_t *buf, size_t num_bytes, size_t *bytes_read);
+ SBError Write(const uint8_t *buf, size_t num_bytes, size_t *bytes_written);
+ SBError Flush();
+ bool IsValid() const;
+ SBError Close();
+
+ operator bool() const;
+ bool operator!() const;
+
+private:
+ FileSP m_opaque_sp;
+};
+
+} // namespace lldb
+
+#endif // LLDB_SBFile_h_
int GetDescriptor() const;
+ static uint32_t GetOptionsFromMode(llvm::StringRef mode);
+
WaitableHandle GetWaitableHandle() override;
FILE *GetStream();
@add_test_categories(['pyapi'])
@no_debug_info_test
+ def test_SBFile(self):
+ sbf = lldb.SBFile()
+ self.assertFalse(sbf.IsValid())
+ self.assertFalse(bool(sbf))
+ e, n = sbf.Write(b'foo')
+ self.assertTrue(e.Fail())
+ self.assertEqual(n, 0)
+ buffer = bytearray(100)
+ e, n = sbf.Read(buffer)
+ self.assertEqual(n, 0)
+ self.assertTrue(e.Fail())
+
+ @add_test_categories(['pyapi'])
+ @no_debug_info_test
def test_SBInstruction(self):
obj = lldb.SBInstruction()
if self.TraceOn():
+"""
+Test lldb Python API for file handles.
+"""
+
+from __future__ import print_function
+
+import os
+import io
+import re
+import sys
+from contextlib import contextmanager
+
+import lldb
+from lldbsuite.test import lldbtest
+from lldbsuite.test.decorators import add_test_categories, no_debug_info_test
+
+
+def readStrippedLines(f):
+ def i():
+ for line in f:
+ line = line.strip()
+ if line:
+ yield line
+ return list(i())
+
+
+class FileHandleTestCase(lldbtest.TestBase):
+
+ mydir = lldbtest.Base.compute_mydir(__file__)
+
+ # The way this class interacts with the debugger is different
+ # than normal. Most of these test cases will mess with the
+ # debugger I/O streams, so we want a fresh debugger for each
+ # test so those mutations don't interfere with each other.
+ #
+ # Also, the way normal tests evaluate debugger commands is
+ # by using a SBCommandInterpreter directly, which captures
+ # the output in a result object. For many of tests tests
+ # we want the debugger to write the output directly to
+ # its I/O streams like it would have done interactively.
+ #
+ # For this reason we also define handleCmd() here, even though
+ # it is similar to runCmd().
+
+ def setUp(self):
+ super(FileHandleTestCase, self).setUp()
+ self.debugger = lldb.SBDebugger.Create()
+ self.out_filename = self.getBuildArtifact('output')
+ self.in_filename = self.getBuildArtifact('input')
+
+ def tearDown(self):
+ lldb.SBDebugger.Destroy(self.debugger)
+ super(FileHandleTestCase, self).tearDown()
+ for name in (self.out_filename, self.in_filename):
+ if os.path.exists(name):
+ os.unlink(name)
+
+ # Similar to runCmd(), but this uses the per-test debugger, and it
+ # supports, letting the debugger just print the results instead
+ # of collecting them.
+ def handleCmd(self, cmd, check=True, collect_result=True):
+ assert not check or collect_result
+ ret = lldb.SBCommandReturnObject()
+ if collect_result:
+ interpreter = self.debugger.GetCommandInterpreter()
+ interpreter.HandleCommand(cmd, ret)
+ else:
+ self.debugger.HandleCommand(cmd)
+ if collect_result and check:
+ self.assertTrue(ret.Succeeded())
+ return ret.GetOutput()
+
+
+ @add_test_categories(['pyapi'])
+ @no_debug_info_test
+ def test_legacy_file_out_script(self):
+ with open(self.out_filename, 'w') as f:
+ self.debugger.SetOutputFileHandle(f, False)
+ # scripts print to output even if you capture the results
+ # I'm not sure I love that behavior, but that's the way
+ # it's been for a long time. That's why this test works
+ # even with collect_result=True.
+ self.handleCmd('script 1+1')
+ self.debugger.GetOutputFileHandle().write('FOO\n')
+ lldb.SBDebugger.Destroy(self.debugger)
+ with open(self.out_filename, 'r') as f:
+ self.assertEqual(readStrippedLines(f), ['2', 'FOO'])
+
+
+ @add_test_categories(['pyapi'])
+ @no_debug_info_test
+ def test_legacy_file_out(self):
+ with open(self.out_filename, 'w') as f:
+ self.debugger.SetOutputFileHandle(f, False)
+ self.handleCmd('p/x 3735928559', collect_result=False, check=False)
+ lldb.SBDebugger.Destroy(self.debugger)
+ with open(self.out_filename, 'r') as f:
+ self.assertIn('deadbeef', f.read())
+
+
+ @add_test_categories(['pyapi'])
+ @no_debug_info_test
+ def test_legacy_file_err(self):
+ with open(self.out_filename, 'w') as f:
+ self.debugger.SetErrorFileHandle(f, False)
+ self.handleCmd('lol', check=False, collect_result=False)
+ lldb.SBDebugger.Destroy(self.debugger)
+ with open(self.out_filename, 'r') as f:
+ self.assertIn("is not a valid command", f.read())
+
+
+ @add_test_categories(['pyapi'])
+ @no_debug_info_test
+ def test_sbfile_type_errors(self):
+ sbf = lldb.SBFile()
+ self.assertRaises(TypeError, sbf.Write, None)
+ self.assertRaises(TypeError, sbf.Read, None)
+ self.assertRaises(TypeError, sbf.Read, b'this bytes is not mutable')
+ self.assertRaises(TypeError, sbf.Write, u"ham sandwich")
+ self.assertRaises(TypeError, sbf.Read, u"ham sandwich")
+
+
+ @add_test_categories(['pyapi'])
+ @no_debug_info_test
+ def test_sbfile_write(self):
+ with open(self.out_filename, 'w') as f:
+ sbf = lldb.SBFile(f.fileno(), "w", False)
+ self.assertTrue(sbf.IsValid())
+ e, n = sbf.Write(b'FOO\nBAR')
+ self.assertTrue(e.Success())
+ self.assertEqual(n, 7)
+ sbf.Close()
+ self.assertFalse(sbf.IsValid())
+ with open(self.out_filename, 'r') as f:
+ self.assertEqual(readStrippedLines(f), ['FOO', 'BAR'])
+
+
+ @add_test_categories(['pyapi'])
+ @no_debug_info_test
+ def test_sbfile_read(self):
+ with open(self.out_filename, 'w') as f:
+ f.write('FOO')
+ with open(self.out_filename, 'r') as f:
+ sbf = lldb.SBFile(f.fileno(), "r", False)
+ self.assertTrue(sbf.IsValid())
+ buffer = bytearray(100)
+ e, n = sbf.Read(buffer)
+ self.assertTrue(e.Success())
+ self.assertEqual(buffer[:n], b'FOO')
+
namespace {
template <class T>
T PyLongAsT(PyObject *obj) {
- static_assert(true, "unsupported type");
+ static_assert(true, "unsupported type");
}
template <> uint64_t PyLongAsT<uint64_t>(PyObject *obj) {
return NULL;
}
}
+
+// These two pybuffer macros are copied out of swig/Lib/python/pybuffer.i,
+// and fixed so they will not crash if PyObject_GetBuffer fails.
+// https://github.com/swig/swig/issues/1640
+
+%define %pybuffer_mutable_binary(TYPEMAP, SIZE)
+%typemap(in) (TYPEMAP, SIZE) {
+ int res; Py_ssize_t size = 0; void *buf = 0;
+ Py_buffer view;
+ res = PyObject_GetBuffer($input, &view, PyBUF_WRITABLE);
+ if (res < 0) {
+ PyErr_Clear();
+ %argument_fail(res, "(TYPEMAP, SIZE)", $symname, $argnum);
+ }
+ size = view.len;
+ buf = view.buf;
+ PyBuffer_Release(&view);
+ $1 = ($1_ltype) buf;
+ $2 = ($2_ltype) (size/sizeof($*1_type));
+}
+%enddef
+
+%define %pybuffer_binary(TYPEMAP, SIZE)
+%typemap(in) (TYPEMAP, SIZE) {
+ int res; Py_ssize_t size = 0; const void *buf = 0;
+ Py_buffer view;
+ res = PyObject_GetBuffer($input, &view, PyBUF_CONTIG_RO);
+ if (res < 0) {
+ PyErr_Clear();
+ %argument_fail(res, "(TYPEMAP, SIZE)", $symname, $argnum);
+ }
+ size = view.len;
+ buf = view.buf;
+ PyBuffer_Release(&view);
+ $1 = ($1_ltype) buf;
+ $2 = ($2_ltype) (size / sizeof($*1_type));
+}
+%enddef
+
+%pybuffer_binary(const uint8_t *buf, size_t num_bytes);
+%pybuffer_mutable_binary(uint8_t *buf, size_t num_bytes);
--- /dev/null
+//===-- SWIG Interface for SBFile -----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+namespace lldb {
+
+%feature("docstring",
+"Represents a file."
+) SBFile;
+
+class SBFile
+{
+public:
+ SBFile();
+ SBFile(int fd, const char *mode, bool transfer_ownership);
+
+ ~SBFile ();
+
+ %feature("autodoc", "Read(buffer) -> SBError, bytes_read") Read;
+ SBError Read(uint8_t *buf, size_t num_bytes, size_t *OUTPUT);
+
+ %feature("autodoc", "Write(buffer) -> SBError, written_read") Write;
+ SBError Write(const uint8_t *buf, size_t num_bytes, size_t *OUTPUT);
+
+ void Flush();
+
+ bool IsValid() const;
+
+ operator bool() const;
+
+ SBError Close();
+};
+
+} // namespace lldb
#include "lldb/API/SBExecutionContext.h"
#include "lldb/API/SBExpressionOptions.h"
#include "lldb/API/SBFileSpec.h"
+#include "lldb/API/SBFile.h"
#include "lldb/API/SBFileSpecList.h"
#include "lldb/API/SBFrame.h"
#include "lldb/API/SBFunction.h"
%include "./interface/SBExecutionContext.i"
%include "./interface/SBExpressionOptions.i"
%include "./interface/SBFileSpec.i"
+%include "./interface/SBFile.i"
%include "./interface/SBFileSpecList.i"
%include "./interface/SBFrame.i"
%include "./interface/SBFunction.i"
SBExecutionContext.cpp
SBExpressionOptions.cpp
SBFileSpec.cpp
+ SBFile.cpp
SBFileSpecList.cpp
SBFrame.cpp
SBFunction.cpp
--- /dev/null
+//===-- SBFile.cpp ------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/API/SBFile.h"
+#include "SBReproducerPrivate.h"
+#include "lldb/API/SBError.h"
+#include "lldb/Host/File.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+SBFile::~SBFile() {}
+
+SBFile::SBFile() { LLDB_RECORD_CONSTRUCTOR_NO_ARGS(SBFile); }
+
+SBFile::SBFile(FILE *file, bool transfer_ownership) {
+ m_opaque_sp = std::make_shared<File>(file, transfer_ownership);
+}
+
+SBFile::SBFile(int fd, const char *mode, bool transfer_owndership) {
+ LLDB_RECORD_CONSTRUCTOR(SBFile, (int, const char *, bool), fd, mode,
+ transfer_owndership);
+ auto options = File::GetOptionsFromMode(mode);
+ m_opaque_sp = std::make_shared<File>(fd, options, transfer_owndership);
+}
+
+SBError SBFile::Read(uint8_t *buf, size_t num_bytes, size_t *bytes_read) {
+ LLDB_RECORD_DUMMY(lldb::SBError, SBFile, Read, (uint8_t *, size_t, size_t *),
+ buf, num_bytes, bytes_read);
+ SBError error;
+ if (!m_opaque_sp) {
+ error.SetErrorString("invalid SBFile");
+ *bytes_read = 0;
+ } else {
+ Status status = m_opaque_sp->Read(buf, num_bytes);
+ error.SetError(status);
+ *bytes_read = num_bytes;
+ }
+ return LLDB_RECORD_RESULT(error);
+}
+
+SBError SBFile::Write(const uint8_t *buf, size_t num_bytes,
+ size_t *bytes_written) {
+ LLDB_RECORD_DUMMY(lldb::SBError, SBFile, Write,
+ (const uint8_t *, size_t, size_t *), buf, num_bytes,
+ bytes_written);
+ SBError error;
+ if (!m_opaque_sp) {
+ error.SetErrorString("invalid SBFile");
+ *bytes_written = 0;
+ } else {
+ Status status = m_opaque_sp->Write(buf, num_bytes);
+ error.SetError(status);
+ *bytes_written = num_bytes;
+ }
+ return LLDB_RECORD_RESULT(error);
+}
+
+SBError SBFile::Flush() {
+ LLDB_RECORD_METHOD_NO_ARGS(lldb::SBError, SBFile, Flush);
+ SBError error;
+ if (!m_opaque_sp) {
+ error.SetErrorString("invalid SBFile");
+ } else {
+ Status status = m_opaque_sp->Flush();
+ error.SetError(status);
+ }
+ return LLDB_RECORD_RESULT(error);
+}
+
+bool SBFile::IsValid() const {
+ LLDB_RECORD_METHOD_CONST_NO_ARGS(bool, SBFile, IsValid);
+ return m_opaque_sp && m_opaque_sp->IsValid();
+}
+
+SBError SBFile::Close() {
+ LLDB_RECORD_METHOD_NO_ARGS(lldb::SBError, SBFile, Close);
+ SBError error;
+ if (m_opaque_sp) {
+ Status status = m_opaque_sp->Close();
+ error.SetError(status);
+ }
+ return LLDB_RECORD_RESULT(error);
+}
+
+SBFile::operator bool() const {
+ LLDB_RECORD_METHOD_CONST_NO_ARGS(bool, SBFile, operator bool);
+ return LLDB_RECORD_RESULT(IsValid());
+}
+
+bool SBFile::operator!() const {
+ LLDB_RECORD_METHOD_CONST_NO_ARGS(bool, SBFile, operator!);
+ return LLDB_RECORD_RESULT(!IsValid());
+}
+
+namespace lldb_private {
+namespace repro {
+template <> void RegisterMethods<SBFile>(Registry &R) {
+ LLDB_REGISTER_CONSTRUCTOR(SBFile, ());
+ LLDB_REGISTER_CONSTRUCTOR(SBFile, (int, const char *, bool));
+ LLDB_REGISTER_METHOD(lldb::SBError, SBFile, Flush, ());
+ LLDB_REGISTER_METHOD_CONST(bool, SBFile, IsValid, ());
+ LLDB_REGISTER_METHOD_CONST(bool, SBFile, operator bool,());
+ LLDB_REGISTER_METHOD_CONST(bool, SBFile, operator!,());
+ LLDB_REGISTER_METHOD(lldb::SBError, SBFile, Close, ());
+}
+} // namespace repro
+} // namespace lldb_private
RegisterMethods<SBEvent>(R);
RegisterMethods<SBExecutionContext>(R);
RegisterMethods<SBExpressionOptions>(R);
+ RegisterMethods<SBFile>(R);
RegisterMethods<SBFileSpec>(R);
RegisterMethods<SBFileSpecList>(R);
RegisterMethods<SBFrame>(R);
return nullptr;
}
+uint32_t File::GetOptionsFromMode(llvm::StringRef mode) {
+ return llvm::StringSwitch<uint32_t>(mode)
+ .Case("r", File::eOpenOptionRead)
+ .Case("w", File::eOpenOptionWrite)
+ .Case("a", File::eOpenOptionWrite | File::eOpenOptionAppend |
+ File::eOpenOptionCanCreate)
+ .Case("r+", File::eOpenOptionRead | File::eOpenOptionWrite)
+ .Case("w+", File::eOpenOptionRead | File::eOpenOptionWrite |
+ File::eOpenOptionCanCreate | File::eOpenOptionTruncate)
+ .Case("a+", File::eOpenOptionRead | File::eOpenOptionWrite |
+ File::eOpenOptionAppend | File::eOpenOptionCanCreate)
+ .Default(0);
+}
+
int File::kInvalidDescriptor = -1;
FILE *File::kInvalidStream = nullptr;
Status File::Close() {
Status error;
- if (StreamIsValid() && m_own_stream) {
- if (::fclose(m_stream) == EOF)
- error.SetErrorToErrno();
+ if (StreamIsValid()) {
+ if (m_own_stream) {
+ if (::fclose(m_stream) == EOF)
+ error.SetErrorToErrno();
+ } else {
+ if (::fflush(m_stream) == EOF)
+ error.SetErrorToErrno();
+ }
}
if (DescriptorIsValid() && m_own_descriptor) {
PythonFile::PythonFile(File &file, const char *mode) { Reset(file, mode); }
-
PythonFile::PythonFile(PyRefType type, PyObject *o) { Reset(type, o); }
PythonFile::~PythonFile() {}
#endif
}
-uint32_t PythonFile::GetOptionsFromMode(llvm::StringRef mode) {
- if (mode.empty())
- return 0;
-
- return llvm::StringSwitch<uint32_t>(mode.str())
- .Case("r", File::eOpenOptionRead)
- .Case("w", File::eOpenOptionWrite)
- .Case("a", File::eOpenOptionWrite | File::eOpenOptionAppend |
- File::eOpenOptionCanCreate)
- .Case("r+", File::eOpenOptionRead | File::eOpenOptionWrite)
- .Case("w+", File::eOpenOptionRead | File::eOpenOptionWrite |
- File::eOpenOptionCanCreate | File::eOpenOptionTruncate)
- .Case("a+", File::eOpenOptionRead | File::eOpenOptionWrite |
- File::eOpenOptionAppend | File::eOpenOptionCanCreate)
- .Default(0);
-}
FileUP PythonFile::GetUnderlyingFile() const {
if (!IsValid())
// We don't own the file descriptor returned by this function, make sure the
// File object knows about that.
PythonString py_mode = GetAttributeValue("mode").AsType<PythonString>();
- auto options = PythonFile::GetOptionsFromMode(py_mode.GetString());
+ auto options = File::GetOptionsFromMode(py_mode.GetString());
auto file = std::make_unique<File>(PyObject_AsFileDescriptor(m_py_obj),
options, false);
if (!file->IsValid())
void Reset(PyRefType type, PyObject *py_obj) override;
void Reset(File &file, const char *mode);
- static uint32_t GetOptionsFromMode(llvm::StringRef mode);
-
lldb::FileUP GetUnderlyingFile() const;
};