[Lldb-commits] [lldb] [lldb] Store the dummy target in the selected execution context (PR #190496)
Jonas Devlieghere via lldb-commits
lldb-commits at lists.llvm.org
Fri Apr 17 17:02:08 PDT 2026
https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/190496
>From 7fafe5168c16e65a5e60de1c7e36a51dfaa40ea6 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sat, 4 Apr 2026 14:48:32 -0700
Subject: [PATCH 1/2] [lldb] Store the dummy target in the selected execution
context
Store the dummy target in the selected execution context. There's no
reason for everybody to have to independently fall back to the dummy
target.
---
lldb/include/lldb/Core/Debugger.h | 6 ++--
.../Commands/CommandObjectDWIMPrint.cpp | 6 ++--
lldb/source/Core/Debugger.cpp | 4 +--
.../source/Interpreter/CommandInterpreter.cpp | 6 +---
lldb/source/Interpreter/CommandObject.cpp | 30 +++++++++----------
lldb/source/Target/Target.cpp | 2 +-
lldb/unittests/Core/DebuggerTest.cpp | 22 ++++++++++++++
7 files changed, 43 insertions(+), 33 deletions(-)
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index fa4483c93e639..23fc34d29b8d7 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -208,12 +208,10 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
}
/// Get the execution context representing the selected entities in the
- /// selected target.
+ /// selected target, or the dummy target if no target is selected.
ExecutionContext GetSelectedExecutionContext();
- /// Similar to GetSelectedExecutionContext but returns a
- /// ExecutionContextRef, and will hold the dummy target if no target is
- /// currently selected.
+ /// Like GetSelectedExecutionContext but returns an ExecutionContextRef.
ExecutionContextRef GetSelectedExecutionContextRef();
/// Get accessor for the target list.
diff --git a/lldb/source/Commands/CommandObjectDWIMPrint.cpp b/lldb/source/Commands/CommandObjectDWIMPrint.cpp
index 27bd71c21ad3f..ca9e9c72f1ba7 100644
--- a/lldb/source/Commands/CommandObjectDWIMPrint.cpp
+++ b/lldb/source/Commands/CommandObjectDWIMPrint.cpp
@@ -75,9 +75,7 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
auto verbosity = GetDebugger().GetDWIMPrintVerbosity();
- Target *target_ptr = m_exe_ctx.GetTargetPtr();
- // Fallback to the dummy target, which can allow for expression evaluation.
- Target &target = target_ptr ? *target_ptr : GetDummyTarget();
+ Target &target = m_exe_ctx.GetTargetRef();
EvaluateExpressionOptions eval_options =
m_expr_options.GetEvaluateExpressionOptions(target, m_varobj_options);
@@ -118,7 +116,7 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
static const std::regex swift_class_regex(
"^<\\S+: 0x[[:xdigit:]]{5,}>\\s*$");
- if (GetDebugger().GetShowDontUsePoHint() && target_ptr &&
+ if (GetDebugger().GetShowDontUsePoHint() && !target.IsDummyTarget() &&
(language.AsLanguageType() == lldb::eLanguageTypeSwift ||
language.IsObjC()) &&
std::regex_match(output.data(), swift_class_regex)) {
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index e1b2ce1b063e0..47a8603a5518e 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -1285,9 +1285,7 @@ void Debugger::RedrawStatusline(
}
ExecutionContext Debugger::GetSelectedExecutionContext() {
- bool adopt_selected = true;
- ExecutionContextRef exe_ctx_ref(GetSelectedTarget().get(), adopt_selected);
- return ExecutionContext(exe_ctx_ref);
+ return ExecutionContext(GetSelectedExecutionContextRef());
}
ExecutionContextRef Debugger::GetSelectedExecutionContextRef() {
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index eeb1ae0ff3eb8..dba1f91c1a561 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -1972,11 +1972,7 @@ Status CommandInterpreter::PreprocessToken(std::string &expr_str) {
Status error;
ExecutionContext exe_ctx(GetExecutionContext());
- // Get a dummy target to allow for calculator mode while processing
- // backticks. This also helps break the infinite loop caused when target is
- // null.
- Target *exe_target = exe_ctx.GetTargetPtr();
- Target &target = exe_target ? *exe_target : m_debugger.GetDummyTarget();
+ Target &target = exe_ctx.GetTargetRef();
ValueObjectSP expr_result_valobj_sp;
diff --git a/lldb/source/Interpreter/CommandObject.cpp b/lldb/source/Interpreter/CommandObject.cpp
index 38a34496e73ff..3b3b2d7a302d9 100644
--- a/lldb/source/Interpreter/CommandObject.cpp
+++ b/lldb/source/Interpreter/CommandObject.cpp
@@ -147,7 +147,12 @@ bool CommandObject::CheckRequirements(CommandReturnObject &result) {
// we don't want any CommandObject instances to keep any of these objects
// around longer than for a single command. Every command should call
// CommandObject::Cleanup() after it has completed.
- assert(!m_exe_ctx.GetTargetPtr());
+ //
+ // The dummy target is allowed here because it is always alive, never causes
+ // resource leaks, and can appear when a command (e.g. "command source") is
+ // invoked re-entrantly before the outer Cleanup() has run.
+ assert(!m_exe_ctx.GetTargetPtr() ||
+ m_exe_ctx.GetTargetPtr()->IsDummyTarget());
assert(!m_exe_ctx.GetProcessPtr());
assert(!m_exe_ctx.GetThreadPtr());
assert(!m_exe_ctx.GetFramePtr());
@@ -162,13 +167,15 @@ bool CommandObject::CheckRequirements(CommandReturnObject &result) {
eCommandRequiresThread | eCommandRequiresFrame |
eCommandTryTargetAPILock)) {
- if ((flags & eCommandRequiresTarget) && !m_exe_ctx.HasTargetScope()) {
+ Target *target = m_exe_ctx.GetTargetPtr();
+ if ((flags & eCommandRequiresTarget) &&
+ (!target || target->IsDummyTarget())) {
result.AppendError(GetInvalidTargetDescription());
return false;
}
if ((flags & eCommandRequiresProcess) && !m_exe_ctx.HasProcessScope()) {
- if (!m_exe_ctx.HasTargetScope())
+ if (!target || target->IsDummyTarget())
result.AppendError(GetInvalidTargetDescription());
else
result.AppendError(GetInvalidProcessDescription());
@@ -176,7 +183,7 @@ bool CommandObject::CheckRequirements(CommandReturnObject &result) {
}
if ((flags & eCommandRequiresThread) && !m_exe_ctx.HasThreadScope()) {
- if (!m_exe_ctx.HasTargetScope())
+ if (!target || target->IsDummyTarget())
result.AppendError(GetInvalidTargetDescription());
else if (!m_exe_ctx.HasProcessScope())
result.AppendError(GetInvalidProcessDescription());
@@ -186,7 +193,7 @@ bool CommandObject::CheckRequirements(CommandReturnObject &result) {
}
if ((flags & eCommandRequiresFrame) && !m_exe_ctx.HasFrameScope()) {
- if (!m_exe_ctx.HasTargetScope())
+ if (!target || target->IsDummyTarget())
result.AppendError(GetInvalidTargetDescription());
else if (!m_exe_ctx.HasProcessScope())
result.AppendError(GetInvalidProcessDescription());
@@ -204,8 +211,7 @@ bool CommandObject::CheckRequirements(CommandReturnObject &result) {
}
if (flags & eCommandTryTargetAPILock) {
- Target *target = m_exe_ctx.GetTargetPtr();
- if (target)
+ if (target && !target->IsDummyTarget())
m_api_locker =
std::unique_lock<std::recursive_mutex>(target->GetAPIMutex());
}
@@ -761,15 +767,7 @@ Target &CommandObject::GetTarget() {
// Fallback to the command interpreter's execution context in case we get
// called after DoExecute has finished. For example, when doing multi-line
// expression that uses an input reader or breakpoint callbacks.
- if (Target *target = m_interpreter.GetExecutionContext().GetTargetPtr())
- return *target;
-
- // Finally, if we have no other target, get the selected target.
- if (TargetSP target_sp = m_interpreter.GetDebugger().GetSelectedTarget())
- return *target_sp;
-
- // We only have the dummy target.
- return GetDummyTarget();
+ return m_interpreter.GetExecutionContext().GetTargetRef();
}
Thread *CommandObject::GetDefaultThread() {
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 0168c7d686e37..538cd6b3a6e7c 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -4440,7 +4440,7 @@ class TargetOptionValueProperties
// we just use the one from this instance.
if (exe_ctx) {
Target *target = exe_ctx->GetTargetPtr();
- if (target) {
+ if (target && !target->IsDummyTarget()) {
TargetOptionValueProperties *target_properties =
static_cast<TargetOptionValueProperties *>(
target->GetValueProperties().get());
diff --git a/lldb/unittests/Core/DebuggerTest.cpp b/lldb/unittests/Core/DebuggerTest.cpp
index 4dccd912c63ae..0897f682c7aa6 100644
--- a/lldb/unittests/Core/DebuggerTest.cpp
+++ b/lldb/unittests/Core/DebuggerTest.cpp
@@ -12,6 +12,7 @@
#include "TestingSupport/TestUtilities.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/HostInfo.h"
+#include "lldb/Target/ExecutionContext.h"
#include "gtest/gtest.h"
using namespace lldb;
@@ -50,3 +51,24 @@ TEST_F(DebuggerTest, TestSettings) {
Debugger::Destroy(debugger_sp);
}
+
+TEST_F(DebuggerTest,
+ SelectedExecutionContextUsesDummyTargetWhenNoTargetSelected) {
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+
+ // No targets have been added, so no target is selected.
+ ASSERT_EQ(debugger_sp->GetSelectedTarget().get(), nullptr);
+
+ Target &dummy_target = debugger_sp->GetDummyTarget();
+
+ // GetSelectedExecutionContextRef should fall back to the dummy target.
+ ExecutionContextRef exe_ctx_ref =
+ debugger_sp->GetSelectedExecutionContextRef();
+ EXPECT_EQ(exe_ctx_ref.GetTargetSP().get(), &dummy_target);
+
+ // GetSelectedExecutionContext should also contain the dummy target.
+ ExecutionContext exe_ctx = debugger_sp->GetSelectedExecutionContext();
+ EXPECT_EQ(exe_ctx.GetTargetPtr(), &dummy_target);
+
+ Debugger::Destroy(debugger_sp);
+}
>From a32615aab9c2b83e10194755f9ce24695d83613a Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Fri, 17 Apr 2026 17:01:54 -0700
Subject: [PATCH 2/2] Make adopt_dummy_target explicit
---
lldb/include/lldb/Core/Debugger.h | 12 ++++++---
lldb/source/Core/Debugger.cpp | 24 ++++++++++++-----
.../source/Interpreter/CommandInterpreter.cpp | 6 +++--
lldb/unittests/Core/DebuggerTest.cpp | 27 ++++++++++++++-----
4 files changed, 50 insertions(+), 19 deletions(-)
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index 23fc34d29b8d7..9dad4b393ec1d 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -208,11 +208,17 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
}
/// Get the execution context representing the selected entities in the
- /// selected target, or the dummy target if no target is selected.
- ExecutionContext GetSelectedExecutionContext();
+ /// selected target. If no target is selected, the execution context will
+ /// contain the dummy target if adopt_dummy_target is true.
+ ///
+ // Ideally, adopt_dummy_target would be the default. However, there are a
+ // bunch of operations that don't make sense on the dummy target but we lack
+ // a mechanism to enforce that. The explicit argument forces the caller to
+ // consider the dummy target.
+ ExecutionContext GetSelectedExecutionContext(bool adopt_dummy_target);
/// Like GetSelectedExecutionContext but returns an ExecutionContextRef.
- ExecutionContextRef GetSelectedExecutionContextRef();
+ ExecutionContextRef GetSelectedExecutionContextRef(bool adopt_dummy_target);
/// Get accessor for the target list.
///
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 47a8603a5518e..efe4a89f72559 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -328,7 +328,8 @@ Status Debugger::SetPropertyValue(const ExecutionContext *exe_ctx,
std::lock_guard<std::mutex> guard(m_statusline_mutex);
if (StatuslineSupported()) {
m_statusline.emplace(*this);
- m_statusline->Enable(GetSelectedExecutionContextRef());
+ m_statusline->Enable(
+ GetSelectedExecutionContextRef(/*adopt_dummy_target=*/true));
} else {
m_statusline.reset();
}
@@ -1270,7 +1271,8 @@ void Debugger::RestoreInputTerminalState() {
{
std::lock_guard<std::mutex> guard(m_statusline_mutex);
if (m_statusline)
- m_statusline->Enable(GetSelectedExecutionContext());
+ m_statusline->Enable(
+ GetSelectedExecutionContext(/*adopt_dummy_target=*/true));
}
}
@@ -1284,15 +1286,22 @@ void Debugger::RedrawStatusline(
m_statusline->Redraw(exe_ctx_ref);
}
-ExecutionContext Debugger::GetSelectedExecutionContext() {
- return ExecutionContext(GetSelectedExecutionContextRef());
+ExecutionContext
+Debugger::GetSelectedExecutionContext(bool adopt_dummy_target) {
+ return ExecutionContext(GetSelectedExecutionContextRef(adopt_dummy_target));
}
-ExecutionContextRef Debugger::GetSelectedExecutionContextRef() {
+ExecutionContextRef
+Debugger::GetSelectedExecutionContextRef(bool adopt_dummy_target) {
if (TargetSP selected_target_sp = GetSelectedTarget())
return ExecutionContextRef(selected_target_sp.get(),
/*adopt_selected=*/true);
- return ExecutionContextRef(m_dummy_target_sp.get(), /*adopt_selected=*/false);
+
+ if (adopt_dummy_target)
+ return ExecutionContextRef(m_dummy_target_sp.get(),
+ /*adopt_selected=*/false);
+
+ return ExecutionContextRef();
}
void Debugger::DispatchInputInterrupt() {
@@ -2228,7 +2237,8 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
std::lock_guard<std::mutex> guard(m_statusline_mutex);
if (!m_statusline) {
m_statusline.emplace(*this);
- m_statusline->Enable(GetSelectedExecutionContextRef());
+ m_statusline->Enable(
+ GetSelectedExecutionContextRef(/*adopt_dummy_target=*/true));
}
}
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index dba1f91c1a561..feea2c867f9cc 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -3256,7 +3256,8 @@ void CommandInterpreter::FindCommandsForApropos(llvm::StringRef search_word,
ExecutionContext CommandInterpreter::GetExecutionContext() const {
return !m_overriden_exe_contexts.empty()
? m_overriden_exe_contexts.top()
- : m_debugger.GetSelectedExecutionContext();
+ : m_debugger.GetSelectedExecutionContext(
+ /*adopt_dummy_target=*/true);
}
void CommandInterpreter::OverrideExecutionContext(
@@ -3382,7 +3383,8 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
StartHandlingCommand();
- ExecutionContext exe_ctx = m_debugger.GetSelectedExecutionContext();
+ ExecutionContext exe_ctx =
+ m_debugger.GetSelectedExecutionContext(/*adopt_dummy_target=*/true);
bool pushed_exe_ctx = false;
if (exe_ctx.HasTargetScope()) {
OverrideExecutionContext(exe_ctx);
diff --git a/lldb/unittests/Core/DebuggerTest.cpp b/lldb/unittests/Core/DebuggerTest.cpp
index 0897f682c7aa6..5283499204313 100644
--- a/lldb/unittests/Core/DebuggerTest.cpp
+++ b/lldb/unittests/Core/DebuggerTest.cpp
@@ -61,14 +61,27 @@ TEST_F(DebuggerTest,
Target &dummy_target = debugger_sp->GetDummyTarget();
- // GetSelectedExecutionContextRef should fall back to the dummy target.
- ExecutionContextRef exe_ctx_ref =
- debugger_sp->GetSelectedExecutionContextRef();
- EXPECT_EQ(exe_ctx_ref.GetTargetSP().get(), &dummy_target);
+ {
+ ExecutionContextRef exe_ctx_ref =
+ debugger_sp->GetSelectedExecutionContextRef(
+ /*adopt_dummy_target=*/true);
+ EXPECT_EQ(exe_ctx_ref.GetTargetSP().get(), &dummy_target);
- // GetSelectedExecutionContext should also contain the dummy target.
- ExecutionContext exe_ctx = debugger_sp->GetSelectedExecutionContext();
- EXPECT_EQ(exe_ctx.GetTargetPtr(), &dummy_target);
+ ExecutionContext exe_ctx =
+ debugger_sp->GetSelectedExecutionContext(/*adopt_dummy_target=*/true);
+ EXPECT_EQ(exe_ctx.GetTargetPtr(), &dummy_target);
+ }
+
+ {
+ ExecutionContextRef exe_ctx_ref =
+ debugger_sp->GetSelectedExecutionContextRef(
+ /*adopt_dummy_target=*/false);
+ EXPECT_EQ(exe_ctx_ref.GetTargetSP().get(), nullptr);
+
+ ExecutionContext exe_ctx =
+ debugger_sp->GetSelectedExecutionContext(/*adopt_dummy_target=*/false);
+ EXPECT_EQ(exe_ctx.GetTargetPtr(), nullptr);
+ }
Debugger::Destroy(debugger_sp);
}
More information about the lldb-commits
mailing list