Med Ismail Bennani e1cd55879b
[lldb] Fix circular dependency and deadlock in scripted frame providers (#187411)
When a scripted frame provider calls back into the thread's frame
machinery (e.g. via HandleCommand or EvaluateExpression), two problems
arise:

1. GetStackFrameList() re-enters the SyntheticStackFrameList
 construction, causing infinite recursion.
2. ClearStackFrames() tries to read-lock the StackFrameList's
 shared_mutex that is already write-locked by GetFramesUpTo,
 causing a deadlock.

This patch fixes those issues by tracking when a provider is actively
fetching frames via a per-host-thread map (m_provider_frames_by_thread)
keyed by HostThread. The map is pushed/popped in
SyntheticStackFrameList::FetchFramesUpTo before calling into the
provider. GetStackFrameList() checks it to route re-entrant calls:

- The provider's own host thread gets the parent frame list, preventing
circular dependency when get_frame_at_index calls back into
GetFrameAtIndex.
- The private state thread also gets the parent frame list, preventing
deadlock when a provider calls EvaluateExpression (which needs the
private state thread to process events).
- Other host threads proceed normally and block on the frame list mutex
until the provider finishes, getting the correct synthetic result.

ClearStackFrames() returns early if any provider is active, since the
frame state is shared and tearing it down while a provider is
mid-construction is both unnecessary and unsafe.

rdar://171558394

Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
2026-03-25 16:22:25 -07:00

68 lines
2.1 KiB
C++

//===-- HostThread.cpp ----------------------------------------------------===//
//
// 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/Host/HostThread.h"
#include "lldb/Host/HostNativeThread.h"
using namespace lldb;
using namespace lldb_private;
HostThread::HostThread() : m_native_thread(new HostNativeThread) {}
HostThread::HostThread(lldb::thread_t thread)
: m_native_thread(new HostNativeThread(thread)) {}
Status HostThread::Join(lldb::thread_result_t *result) {
return m_native_thread->Join(result);
}
Status HostThread::Cancel() { return m_native_thread->Cancel(); }
void HostThread::Reset() { return m_native_thread->Reset(); }
lldb::thread_t HostThread::Release() { return m_native_thread->Release(); }
bool HostThread::IsJoinable() const { return m_native_thread->IsJoinable(); }
HostNativeThread &HostThread::GetNativeThread() {
return static_cast<HostNativeThread &>(*m_native_thread);
}
const HostNativeThread &HostThread::GetNativeThread() const {
return static_cast<const HostNativeThread &>(*m_native_thread);
}
lldb::thread_result_t HostThread::GetResult() const {
return m_native_thread->GetResult();
}
bool HostThread::EqualsThread(lldb::thread_t thread) const {
return m_native_thread->EqualsThread(thread);
}
bool HostThread::EqualsThread(const HostThread &thread) const {
return m_native_thread->EqualsThread(
thread.GetNativeThread().GetSystemHandle());
}
bool HostThread::HasThread() const {
if (!m_native_thread)
return false;
return m_native_thread->GetSystemHandle() != LLDB_INVALID_HOST_THREAD;
}
unsigned llvm::DenseMapInfo<HostThread>::getHashValue(const HostThread &val) {
return DenseMapInfo<thread_t>::getHashValue(
val.GetNativeThread().GetSystemHandle());
}
bool llvm::DenseMapInfo<HostThread>::isEqual(const HostThread &lhs,
const HostThread &rhs) {
return lhs.EqualsThread(rhs);
}