Skip to content

Commit 0a07c96

Browse files
committed
[lldb/python] Fix dangling Event and CommandReturnObject references
Unlike the rest of our SB objects, SBEvent and SBCommandReturnObject have the ability to hold non-owning pointers to their non-SB counterparts. This makes it hard to ensure the SB objects do not become dangling once their backing object goes away. While we could make these two objects behave like others, that would require plubming even more shared pointers through our internal code (Event objects are mostly prepared for it, CommandReturnObject are not). Doing so seems unnecessarily disruptive, given that (unlike for some of the other objects) I don't see any good reason why would someone want to hold onto these objects after the function terminates. For that reason, this patch implements a different approach -- the SB objects will still hold non-owning pointers, but they will be reset to the empty/default state as soon as the function terminates. This python code will not crash if the user decides to store these objects -- but the objects themselves will be useless/empty. Differential Revision: https://reviews.llvm.org/D116162
1 parent 882c083 commit 0a07c96

File tree

4 files changed

+54
-22
lines changed

4 files changed

+54
-22
lines changed

lldb/bindings/python/python-swigsafecast.swig

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
namespace lldb_private {
22
namespace python {
33

4-
PyObject *SBTypeToSWIGWrapper(lldb::SBEvent &event_sb) {
5-
return SWIG_NewPointerObj(&event_sb, SWIGTYPE_p_lldb__SBEvent, 0);
6-
}
7-
8-
PyObject *SBTypeToSWIGWrapper(lldb::SBCommandReturnObject &cmd_ret_obj_sb) {
9-
return SWIG_NewPointerObj(&cmd_ret_obj_sb,
10-
SWIGTYPE_p_lldb__SBCommandReturnObject, 0);
11-
}
12-
134
PythonObject ToSWIGHelper(void *obj, swig_type_info *info) {
145
return {PyRefType::Owned, SWIG_NewPointerObj(obj, info, SWIG_POINTER_OWN)};
156
}
167

8+
/// A class that automatically clears an SB object when it goes out of scope.
9+
/// Use for cases where the SB object points to a temporary/unowned entity.
10+
template <typename T> class ScopedPythonObject : PythonObject {
11+
public:
12+
ScopedPythonObject(T *sb, swig_type_info *info)
13+
: PythonObject(ToSWIGHelper(sb, info)), m_sb(sb) {}
14+
~ScopedPythonObject() {
15+
if (m_sb)
16+
*m_sb = T();
17+
}
18+
ScopedPythonObject(ScopedPythonObject &&rhs)
19+
: PythonObject(std::move(rhs)), m_sb(std::exchange(rhs.m_sb, nullptr)) {}
20+
ScopedPythonObject(const ScopedPythonObject &) = delete;
21+
ScopedPythonObject &operator=(const ScopedPythonObject &) = delete;
22+
ScopedPythonObject &operator=(ScopedPythonObject &&) = delete;
23+
24+
const PythonObject &obj() const { return *this; }
25+
26+
private:
27+
T *m_sb;
28+
};
29+
1730
PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBValue> value_sb) {
1831
return ToSWIGHelper(value_sb.release(), SWIGTYPE_p_lldb__SBValue);
1932
}
@@ -94,5 +107,17 @@ PythonObject ToSWIGWrapper(const SymbolContext &sym_ctx) {
94107
SWIGTYPE_p_lldb__SBSymbolContext);
95108
}
96109

110+
ScopedPythonObject<lldb::SBCommandReturnObject>
111+
ToSWIGWrapper(CommandReturnObject &cmd_retobj) {
112+
return ScopedPythonObject<lldb::SBCommandReturnObject>(
113+
new lldb::SBCommandReturnObject(cmd_retobj),
114+
SWIGTYPE_p_lldb__SBCommandReturnObject);
115+
}
116+
117+
ScopedPythonObject<lldb::SBEvent> ToSWIGWrapper(Event *event) {
118+
return ScopedPythonObject<lldb::SBEvent>(new lldb::SBEvent(event),
119+
SWIGTYPE_p_lldb__SBEvent);
120+
}
121+
97122
} // namespace python
98123
} // namespace lldb_private

lldb/bindings/python/python-wrapper.swig

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -376,9 +376,8 @@ bool lldb_private::LLDBSWIGPythonCallThreadPlan(
376376

377377
PythonObject result;
378378
if (event != nullptr) {
379-
lldb::SBEvent sb_event(event);
380-
PythonObject event_arg(PyRefType::Owned, SBTypeToSWIGWrapper(sb_event));
381-
result = pfunc(event_arg);
379+
ScopedPythonObject<SBEvent> event_arg = ToSWIGWrapper(event);
380+
result = pfunc(event_arg.obj());
382381
} else
383382
result = pfunc();
384383

@@ -795,7 +794,6 @@ bool lldb_private::LLDBSwigPythonCallCommand(
795794
lldb::DebuggerSP debugger, const char *args,
796795
lldb_private::CommandReturnObject &cmd_retobj,
797796
lldb::ExecutionContextRefSP exe_ctx_ref_sp) {
798-
lldb::SBCommandReturnObject cmd_retobj_sb(cmd_retobj);
799797

800798
PyErr_Cleaner py_err_cleaner(true);
801799
auto dict = PythonModule::MainModule().ResolveName<PythonDictionary>(
@@ -812,14 +810,13 @@ bool lldb_private::LLDBSwigPythonCallCommand(
812810
return false;
813811
}
814812
PythonObject debugger_arg = ToSWIGWrapper(std::move(debugger));
815-
PythonObject cmd_retobj_arg(PyRefType::Owned,
816-
SBTypeToSWIGWrapper(cmd_retobj_sb));
813+
auto cmd_retobj_arg = ToSWIGWrapper(cmd_retobj);
817814

818815
if (argc.get().max_positional_args < 5u)
819-
pfunc(debugger_arg, PythonString(args), cmd_retobj_arg, dict);
816+
pfunc(debugger_arg, PythonString(args), cmd_retobj_arg.obj(), dict);
820817
else
821818
pfunc(debugger_arg, PythonString(args),
822-
ToSWIGWrapper(std::move(exe_ctx_ref_sp)), cmd_retobj_arg, dict);
819+
ToSWIGWrapper(std::move(exe_ctx_ref_sp)), cmd_retobj_arg.obj(), dict);
823820

824821
return true;
825822
}
@@ -828,7 +825,6 @@ bool lldb_private::LLDBSwigPythonCallCommandObject(
828825
PyObject *implementor, lldb::DebuggerSP debugger, const char *args,
829826
lldb_private::CommandReturnObject &cmd_retobj,
830827
lldb::ExecutionContextRefSP exe_ctx_ref_sp) {
831-
lldb::SBCommandReturnObject cmd_retobj_sb(cmd_retobj);
832828

833829
PyErr_Cleaner py_err_cleaner(true);
834830

@@ -838,11 +834,10 @@ bool lldb_private::LLDBSwigPythonCallCommandObject(
838834
if (!pfunc.IsAllocated())
839835
return false;
840836

841-
PythonObject cmd_retobj_arg(PyRefType::Owned,
842-
SBTypeToSWIGWrapper(cmd_retobj_sb));
837+
auto cmd_retobj_arg = ToSWIGWrapper(cmd_retobj);
843838

844839
pfunc(ToSWIGWrapper(std::move(debugger)), PythonString(args),
845-
ToSWIGWrapper(exe_ctx_ref_sp), cmd_retobj_arg);
840+
ToSWIGWrapper(exe_ctx_ref_sp), cmd_retobj_arg.obj());
846841

847842
return true;
848843
}

lldb/test/API/commands/command/script/TestCommandScript.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,17 @@ def cleanup():
167167
self.runCmd('bug11569', check=False)
168168

169169
def test_persistence(self):
170+
"""
171+
Ensure that function arguments meaningfully persist (and do not crash!)
172+
even after the function terminates.
173+
"""
170174
self.runCmd("command script import persistence.py")
171175
self.runCmd("command script add -f persistence.save_debugger save_debugger")
172176
self.expect("save_debugger", substrs=[str(self.dbg)])
177+
178+
# After the command completes, the debugger object should still be
179+
# valid.
173180
self.expect("script str(persistence.debugger_copy)", substrs=[str(self.dbg)])
181+
# The result object will be replaced by an empty result object (in the
182+
# "Started" state).
183+
self.expect("script str(persistence.result_copy)", substrs=["Started"])
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import lldb
22

33
debugger_copy = None
4+
result_copy = None
45

56
def save_debugger(debugger, command, context, result, internal_dict):
6-
global debugger_copy
7+
global debugger_copy, result_copy
78
debugger_copy = debugger
9+
result_copy = result
810
result.AppendMessage(str(debugger))
911
result.SetStatus(lldb.eReturnStatusSuccessFinishResult)

0 commit comments

Comments
 (0)