[Lldb-commits] [lldb] [lldb] Fix nullptr deref in ThreadPlanCallFunction::DoTakedown (PR #195910)
Raphael Isemann via lldb-commits
lldb-commits at lists.llvm.org
Thu May 7 02:26:23 PDT 2026
https://github.com/Teemperor updated https://github.com/llvm/llvm-project/pull/195910
>From 19af6669a4b1fec950811ecbdce1e630da1b7013 Mon Sep 17 00:00:00 2001
From: Raphael Isemann <rise at apple.com>
Date: Tue, 5 May 2026 19:14:38 +0100
Subject: [PATCH] [lldb] Fix nullptr deref in
ThreadPlanCallFunction::DoTakedown
When running an expression that calls `exit`, we can end up in a case
where we call `ThreadPlan::GetThread` without having a current thread.
The backtrace for this looks like this:
```
ThreadPlan::GetThread
ThreadPlanCallFunction::DoTakedown
ThreadPlanCallFunction::~ThreadPlanCallFunction
ThreadPlanCallUserExpression::~ThreadPlanCallUserExpression
```
The problem is that `ThreadPlan::GetThread` returns a `Thread &` and
cannot communicate that there is no thread to be returned.
In practice, what we end up returning is a nullptr dereferenced to
a nullptr reference, which is UB. This then causes the associated
test TestExitDuringExpression.py to fail with UBSan.
This patch just avoids calling `ThreadPlan::GetThread` in this
one specific edge case to avoid the issue. It also adds an assert to
make this an error we can see without having to run UBSan.
I didn't change the `ThreadPlan::GetThread` API as I can only see
this one specific edge case where we can encounter this situation.
If we made `ThreadPlan::GetThread` return an optional-like value,
then we had to check the result in about 50 different places, and
all of that code would be effectively unreachable then.
---
lldb/source/Target/ThreadPlan.cpp | 1 +
lldb/source/Target/ThreadPlanCallFunction.cpp | 5 +++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/lldb/source/Target/ThreadPlan.cpp b/lldb/source/Target/ThreadPlan.cpp
index 26ffbf84be6f0..df42da9df84e0 100644
--- a/lldb/source/Target/ThreadPlan.cpp
+++ b/lldb/source/Target/ThreadPlan.cpp
@@ -45,6 +45,7 @@ Thread &ThreadPlan::GetThread() {
ThreadSP thread_sp = m_process.GetThreadList().FindThreadByID(m_tid);
m_thread = thread_sp.get();
+ assert(m_thread);
return *m_thread;
}
diff --git a/lldb/source/Target/ThreadPlanCallFunction.cpp b/lldb/source/Target/ThreadPlanCallFunction.cpp
index 427693f02722f..d0b9a2fca446d 100644
--- a/lldb/source/Target/ThreadPlanCallFunction.cpp
+++ b/lldb/source/Target/ThreadPlanCallFunction.cpp
@@ -174,14 +174,14 @@ void ThreadPlanCallFunction::ReportRegisterState(const char *message) {
void ThreadPlanCallFunction::DoTakedown(bool success) {
Log *log = GetLog(LLDBLog::Step);
- Thread &thread = GetThread();
if (!m_valid) {
// If ConstructorSetup was succesfull but PrepareTrivialCall was not,
// we will have a saved register state and potentially modified registers.
// Restore those.
if (m_stored_thread_state.register_backup_sp)
- if (!thread.RestoreRegisterStateFromCheckpoint(m_stored_thread_state))
+ if (!GetThread().RestoreRegisterStateFromCheckpoint(
+ m_stored_thread_state))
LLDB_LOGF(
log,
"ThreadPlanCallFunction(%p): Failed to restore register state from "
@@ -205,6 +205,7 @@ void ThreadPlanCallFunction::DoTakedown(bool success) {
"0x%4.4" PRIx64 ", m_valid: %d complete: %d.\n",
static_cast<void *>(this), m_tid, m_valid, IsPlanComplete());
m_takedown_done = true;
+ Thread &thread = GetThread();
m_stop_address =
thread.GetStackFrameAtIndex(0)->GetRegisterContext()->GetPC();
m_real_stop_info_sp = GetPrivateStopInfo();
More information about the lldb-commits
mailing list