[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:13:31 PDT 2026
https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/225312
>From 870f677c4043f5c5dc253473080232422687032c Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Tue, 22 Sep 2026 00:13:01 -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 3fe311f215d0 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 (e1cd55879b5f) 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.
So 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.
Checking 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 ends up broader than the check it replaces: synthetic
children, OS plugin thread lists and scripted thread plans are now
covered too.
Scripted commands keep both capabilities. UserCanRunDirectly() is true
for them, so the scope never gets pushed, 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 | 14 +++++++++++++-
lldb/source/Target/Target.cpp | 12 +-----------
lldb/source/Utility/Policy.cpp | 6 ++++++
lldb/unittests/Utility/PolicyTest.cpp | 13 +++++++++++++
6 files changed, 43 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..d8c05b72bcb53 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,18 @@ 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);
+ thread_plan_sp->SetStopOthers(true);
+ } 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