[Lldb-commits] [lldb] [lldb] Consult Policy for expression evaluation capabilities (PR #225312)
Med Ismail Bennani via lldb-commits
lldb-commits at lists.llvm.org
Tue Sep 22 00:43:59 PDT 2026
https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/225312
>From 6ea5bd81c10f1654e699e5b00401901f1357e778 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Tue, 22 Sep 2026 00:43:42 -0700
Subject: [PATCH] [lldb] Consult Policy for expression evaluation capabilities
can_evaluate_expressions, can_run_all_threads and can_try_all_threads
have been unused since 3fe311f added them: nothing outside Policy.h and
its unit test ever read them.
Two of them already had a hand-rolled equivalent, though.
Target::EvaluateExpression forces single-threaded execution while a
frame provider is active, which is what the two thread capabilities
describe. That check predates them by six weeks (e1cd558) and the
adoption patch never came back for it. Living in Target also means it
only sees expressions that go through Target, not the ones coming from
FunctionCaller or IRInterpreter.
This patch changes that to consult the capabilities where the decision
is actually made: UserExpression::Evaluate for can_evaluate_expressions,
and RunThreadPlan for the other two, narrowing the caller's options once
up front rather than at each use. The Target check goes away.
It also checks the thread capabilities in CreateScriptedExtensionCall
rather than somewhere frame-provider specific. Not altering the state
you were asked to describe applies to any extension the debugger calls
into, so this increases the scope of the check it replaces: synthetic
children, OS plugin thread lists and scripted thread plans are now
covered too.
Since scripted commands set UserCanRunDirectly to true, the
ScriptedExtensionCall policy never gets pushed, so these expression
evaluation capabilities are kept and that is the behavior we want: the
user invoked the command directly, so there is no debugger operation
already in flight whose state it could alter.
rdar://176223894
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
lldb/include/lldb/Target/Process.h | 2 +-
lldb/source/Expression/UserExpression.cpp | 9 +++++++++
lldb/source/Target/Process.cpp | 13 ++++++++++++-
lldb/source/Target/Target.cpp | 12 +-----------
lldb/source/Utility/Policy.cpp | 6 ++++++
lldb/unittests/Utility/PolicyTest.cpp | 13 +++++++++++++
6 files changed, 42 insertions(+), 13 deletions(-)
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index c530204e77044..1f156bd28ab2b 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -1292,7 +1292,7 @@ class Process : public std::enable_shared_from_this<Process>,
lldb::ExpressionResults
RunThreadPlan(ExecutionContext &exe_ctx, lldb::ThreadPlanSP &thread_plan_sp,
- const EvaluateExpressionOptions &options,
+ const EvaluateExpressionOptions &requested_options,
DiagnosticManager &diagnostic_manager);
void GetStatus(Stream &ostrm, bool is_verbose = false);
diff --git a/lldb/source/Expression/UserExpression.cpp b/lldb/source/Expression/UserExpression.cpp
index 537a9cc4c4e35..8b4cab94887ed 100644
--- a/lldb/source/Expression/UserExpression.cpp
+++ b/lldb/source/Expression/UserExpression.cpp
@@ -38,6 +38,7 @@
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
+#include "lldb/Utility/Policy.h"
#include "lldb/Utility/State.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/ValueObject/ValueObjectConstResult.h"
@@ -152,6 +153,14 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx,
exe_ctx.GetBestExecutionContextScope(), std::move(error));
};
+ if (!PolicyStack::Get().Current().capabilities.can_evaluate_expressions) {
+ LLDB_LOG(log, "== [UserExpression::Evaluate] The current policy doesn't "
+ "allow evaluating expressions ==");
+ set_error(Status::FromErrorString(
+ "expression evaluation is not allowed in this context"));
+ return lldb::eExpressionSetupError;
+ }
+
if (ctx_obj) {
static unsigned const ctx_type_mask = lldb::TypeFlags::eTypeIsClass |
lldb::TypeFlags::eTypeIsStructUnion |
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 1b3715917e947..529668d5b257d 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -5184,7 +5184,7 @@ HandleStoppedEvent(lldb::tid_t thread_id, const ThreadPlanSP &thread_plan_sp,
ExpressionResults
Process::RunThreadPlan(ExecutionContext &exe_ctx,
lldb::ThreadPlanSP &thread_plan_sp,
- const EvaluateExpressionOptions &options,
+ const EvaluateExpressionOptions &requested_options,
DiagnosticManager &diagnostic_manager) {
ExpressionResults return_value = eExpressionSetupError;
@@ -5220,6 +5220,17 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
// to run the expression exits during the expression evaluation.
lldb::tid_t expr_thread_id = thread->GetID();
+ // The all-threads retry exists only to resume the other threads, so it is
+ // coupled to can_run_all_threads rather than checked independently.
+ EvaluateExpressionOptions options = requested_options;
+ const Policy policy = PolicyStack::Get().Current();
+ if (!policy.capabilities.can_run_all_threads) {
+ options.SetStopOthers(true);
+ options.SetTryAllThreads(false);
+ } else if (!policy.capabilities.can_try_all_threads) {
+ options.SetTryAllThreads(false);
+ }
+
// We need to change some of the thread plan attributes for the thread plan
// runner. This will restore them when we are done:
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 20c94d98eca56..fde9e1d056117 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -3003,19 +3003,9 @@ ExpressionResults Target::EvaluateExpression(
result_valobj_sp = persistent_var_sp->GetValueObject();
execution_results = eExpressionCompleted;
} else {
- // If this expression is being evaluated from inside a frame provider,
- // force single-thread execution. Resuming all threads while a provider
- // is mid-construction could cause unwanted process state changes.
- EvaluateExpressionOptions effective_options = options;
- if (ThreadSP thread_sp = exe_ctx.GetThreadSP()) {
- if (thread_sp->IsAnyProviderActive()) {
- effective_options.SetStopOthers(true);
- effective_options.SetTryAllThreads(false);
- }
- }
llvm::StringRef prefix = GetExpressionPrefixContents();
execution_results =
- UserExpression::Evaluate(exe_ctx, effective_options, expr, prefix,
+ UserExpression::Evaluate(exe_ctx, options, expr, prefix,
result_valobj_sp, fixed_expression, ctx_obj);
}
diff --git a/lldb/source/Utility/Policy.cpp b/lldb/source/Utility/Policy.cpp
index 04293d7a03f85..bd17bf90bd59e 100644
--- a/lldb/source/Utility/Policy.cpp
+++ b/lldb/source/Utility/Policy.cpp
@@ -64,9 +64,15 @@ Policy Policy::CreatePublicStateRunningExpression() {
return p;
}
+// A scripted extension invoked by the debugger must not perturb the state it
+// was asked to describe, so an expression started from one stays on its own
+// thread. This scope is not pushed for scripted commands, which the user
+// invokes directly.
Policy Policy::CreateScriptedExtensionCall() {
Policy p = PolicyStack::Get().Current();
p.capabilities.can_bypass_target_api_mutex = true;
+ p.capabilities.can_run_all_threads = false;
+ p.capabilities.can_try_all_threads = false;
return p;
}
diff --git a/lldb/unittests/Utility/PolicyTest.cpp b/lldb/unittests/Utility/PolicyTest.cpp
index 66c08f24eb5a5..cc514b879f5fd 100644
--- a/lldb/unittests/Utility/PolicyTest.cpp
+++ b/lldb/unittests/Utility/PolicyTest.cpp
@@ -74,11 +74,24 @@ TEST(PolicyTest, PublicStateRunningExpression) {
TEST(PolicyTest, ScriptedExtensionCall) {
Policy p = Policy::CreateScriptedExtensionCall();
EXPECT_TRUE(p.capabilities.can_bypass_target_api_mutex);
+ EXPECT_FALSE(p.capabilities.can_run_all_threads);
+ EXPECT_FALSE(p.capabilities.can_try_all_threads);
+ // An extension may still evaluate expressions and run commands; it just
+ // can't let the inferior's other threads run while doing so.
+ EXPECT_TRUE(p.capabilities.can_evaluate_expressions);
PolicyStack::Guard guard = PolicyStack::Get().PushPrivateState();
Policy nested = Policy::CreateScriptedExtensionCall();
EXPECT_EQ(nested.view, Policy::View::Private);
EXPECT_TRUE(nested.capabilities.can_bypass_target_api_mutex);
+ EXPECT_FALSE(nested.capabilities.can_run_all_threads);
+}
+
+TEST(PolicyTest, ScriptedExtensionCallWithdrawalIsInherited) {
+ PolicyStack::Guard guard = PolicyStack::Get().PushScriptedExtensionCall();
+ Policy nested = Policy::CreatePrivateState();
+ EXPECT_FALSE(nested.capabilities.can_run_all_threads);
+ EXPECT_FALSE(nested.capabilities.can_try_all_threads);
}
TEST(PolicyTest, StackDefaultIsPublicState) {
More information about the lldb-commits
mailing list