Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion lldb/bindings/lua/lua-typemaps.swig
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,27 @@ LLDB_NUMBER_TYPEMAP(enum SWIGTYPE);
$1 = (char *)malloc($2);
}

// Disable default type checking for this method to avoid SWIG dispatch issues.
//
// Problem: SBThread::GetStopDescription has two overloads:
// 1. GetStopDescription(char* dst_or_null, size_t dst_len)
// 2. GetStopDescription(lldb::SBStream& stream)
//
// SWIG generates a dispatch function to select the correct overload based on argument types.
// see https://www.swig.org/Doc4.0/SWIGDocumentation.html#Typemaps_overloading.
// However, this dispatcher doesn't consider typemaps that transform function signatures.
//
// In lua, our typemap converts GetStopDescription(char*, size_t) to GetStopDescription(int).
// The dispatcher still checks against the original (char*, size_t) signature instead of
// the transformed (int) signature, causing type matching to fail.
// This only affects SBThread::GetStopDescription since the type check also matches
// the argument name, which is unique to this function.
%typemap(typecheck, precedence=SWIG_TYPECHECK_POINTER) (char *dst_or_null, size_t dst_len) ""

%typemap(argout) (char *dst_or_null, size_t dst_len) {
lua_pop(L, 1); // Blow away the previous result
lua_pushlstring(L, (const char *)$1, $result);
llvm::StringRef ref($1);
lua_pushlstring(L, (const char *)$1, ref.size());
free($1);
// SWIG_arg was already incremented
}
Expand Down
18 changes: 18 additions & 0 deletions lldb/bindings/python/python-typemaps.swig
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,24 @@ AND call SWIG_fail at the same time, because it will result in a double free.
}
$1 = (char *)malloc($2);
}

// Disable default type checking for this method to avoid SWIG dispatch issues.
//
// Problem: SBThread::GetStopDescription has two overloads:
// 1. GetStopDescription(char* dst_or_null, size_t dst_len)
// 2. GetStopDescription(lldb::SBStream& stream)
//
// SWIG generates a dispatch function to select the correct overload based on argument types.
// see https://www.swig.org/Doc4.0/SWIGDocumentation.html#Typemaps_overloading.
// However, this dispatcher doesn't consider typemaps that transform function signatures.
//
// In Python, our typemap converts GetStopDescription(char*, size_t) to GetStopDescription(int).
// The dispatcher still checks against the original (char*, size_t) signature instead of
// the transformed (int) signature, causing type matching to fail.
// This only affects SBThread::GetStopDescription since the type check also matches
// the argument name, which is unique to this function.
%typemap(typecheck, precedence=SWIG_TYPECHECK_POINTER) (char *dst_or_null, size_t dst_len) ""
Copy link
Member

@Michael137 Michael137 Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth mentioning that this is required because the other overload has a numinputs=1 typemap. And the generated dispatching function will try to type check that as a char*, where in actuality it's an integer (correct me if I'm wrong).

Does this mean it removes the type-check for all the other APIs with this signature too? Is that an issue?

Copy link
Contributor Author

@da-viper da-viper Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the comment.

Does this mean it removes the type-check for all the other APIs with this signature too? Is that an issue?

Only if it matches the argument name and type. But now no other function does.

could not find a way to limit it to a class without redefining the class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also see:

// For lldb::SBFileSpec::GetPath
%typemap(in) (char *dst_path, size_t dst_len) = (char *dst_or_null, size_t dst_len);
%typemap(argout) (char *dst_path, size_t dst_len) = (char *dst_or_null, size_t dst_len);

I'm not too familiar with SWIG, so correct me if I'm wrong. This just copies the typemap, but not the typecheck itself? And regardless, is typecheck only ever applicable to these dispatch functions? In which case, GetPath wouldn't really be affected by that even if it did copy the typecheck?

Unrelated: looks like SBFileSpec::GetPath suffers from the same kind of API problem where you have to supply a fixed size. Should we add an SBStream overload here too? In which case, copying the typemap would be beneficial anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just copies the typemap, but not the typecheck itself

Yes

, is typecheck only ever applicable to these dispatch functions?

No because it copied for only typemap(in) and type map(argout)

SBFileSpec::GetPath suffers from the same kind of API problem where you have to supply a fixed size. Should we add an SBStream overload here too?

Would make sense to do so


%typemap(argout) (char *dst_or_null, size_t dst_len) {
Py_XDECREF($result); /* Blow away any previous result */
llvm::StringRef ref($1);
Expand Down
8 changes: 8 additions & 0 deletions lldb/include/lldb/API/SBThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ class LLDB_API SBThread {
SBThreadCollection
GetStopReasonExtendedBacktraces(InstrumentationRuntimeType type);

/// Gets a human-readable description of why the thread stopped.
///
/// \param stream Output stream to receive the stop description text
/// \return
/// true if obtained and written to the stream,
// false if there was an error retrieving the description.
bool GetStopDescription(lldb::SBStream &stream) const;

size_t GetStopDescription(char *dst_or_null, size_t dst_len);

SBValue GetStopReturnValue();
Expand Down
35 changes: 29 additions & 6 deletions lldb/source/API/SBThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,34 @@ SBThread::GetStopReasonExtendedBacktraces(InstrumentationRuntimeType type) {
return threads;
}

size_t SBThread::GetStopDescription(char *dst, size_t dst_len) {
LLDB_INSTRUMENT_VA(this, dst, dst_len);
bool SBThread::GetStopDescription(lldb::SBStream &stream) const {
LLDB_INSTRUMENT_VA(this, stream);

if (!m_opaque_sp)
return false;

llvm::Expected<StoppedExecutionContext> exe_ctx =
GetStoppedExecutionContext(m_opaque_sp);
if (!exe_ctx) {
LLDB_LOG_ERROR(GetLog(LLDBLog::API), exe_ctx.takeError(), "{0}");
return false;
}

if (!exe_ctx->HasThreadScope())
return false;

Stream &strm = stream.ref();
const std::string stop_desc = exe_ctx->GetThreadPtr()->GetStopDescription();
strm.PutCString(stop_desc);

return true;
}

size_t SBThread::GetStopDescription(char *dst_or_null, size_t dst_len) {
LLDB_INSTRUMENT_VA(this, dst_or_null, dst_len);

if (dst)
*dst = 0;
if (dst_or_null)
*dst_or_null = 0;

llvm::Expected<StoppedExecutionContext> exe_ctx =
GetStoppedExecutionContext(m_opaque_sp);
Expand All @@ -259,8 +282,8 @@ size_t SBThread::GetStopDescription(char *dst, size_t dst_len) {
if (thread_stop_desc.empty())
return 0;

if (dst)
return ::snprintf(dst, dst_len, "%s", thread_stop_desc.c_str()) + 1;
if (dst_or_null)
return ::snprintf(dst_or_null, dst_len, "%s", thread_stop_desc.c_str()) + 1;

// NULL dst passed in, return the length needed to contain the
// description.
Expand Down
25 changes: 25 additions & 0 deletions lldb/test/API/lua_api/TestThreadAPI.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
_T = require('lua_lldb_test').create_test('TestThreadAPI')

function _T:TestGetStopDescription()
local target = self:create_target()
local breakpoint = target:BreakpointCreateByName("main", "a.out")
assertTrue(breakpoint:IsValid() and breakpoint:GetNumLocations() == 1)

local process = target:LaunchSimple({ 'arg1', 'arg2' }, nil, nil)
local thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
assertNotNil(thread)
assertTrue(thread:IsValid())

assertEqual("breakpoint", thread:GetStopDescription(string.len("breakpoint") + 1))
assertEqual("break", thread:GetStopDescription(string.len("break") + 1))
assertEqual("b", thread:GetStopDescription(string.len("b") + 1))
assertEqual("breakpoint 1.1", thread:GetStopDescription(string.len("breakpoint 1.1") + 100))

-- Test stream variation
local stream = lldb.SBStream()
assertTrue(thread:GetStopDescription(stream))
assertNotNil(stream)
assertEqual("breakpoint 1.1", stream:GetData())
end

os.exit(_T:run())
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def fuzz_obj(obj):
obj.GetStopReasonDataCount()
obj.GetStopReasonDataAtIndex(100)
obj.GetStopDescription(256)
obj.GetStopDescription(lldb.SBStream())
obj.GetThreadID()
obj.GetIndexID()
obj.GetName()
Expand Down
5 changes: 5 additions & 0 deletions lldb/test/API/python_api/thread/TestThreadAPI.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ def get_stop_description(self):
"breakpoint 1.1", thread.GetStopDescription(len("breakpoint 1.1") + 100)
)

# Test the stream variation
stream = lldb.SBStream()
self.assertTrue(thread.GetStopDescription(stream))
self.assertEqual("breakpoint 1.1", stream.GetData())

def step_out_of_malloc_into_function_b(self, exe_name):
"""Test Python SBThread.StepOut() API to step out of a malloc call where the call site is at function b()."""
exe = self.getBuildArtifact(exe_name)
Expand Down
Loading