[lldb][Expression] Add API to set/get language-specific expression options (#179208)

The motivation here is that we don't want to pollute the SBAPI with
getters/setters for expression evaluation options that only apply to a
single language. The ultimate goal would be to have plugins register
additional options to the `expression` command when the plugin is
loaded. This patch only provides the minimal `SBExpressionOptions`
interface to set an option with an arbitrary name, which the language
plugin knows how to interpret. The underlying options dictionary is an
`StructuredData::Dictionary` so we can map strings to values of any
type. But the SBAPI just exposes setting a boolean value. Future
overloads of `SetLanguageOption` can provide setters for more types.

The boolean setter/getter will be used for the C++-specific option being
introduced in: https://github.com/llvm/llvm-project/pull/177926
This commit is contained in:
Michael Buch 2026-02-04 21:32:14 +00:00 committed by GitHub
parent c51a758d7f
commit 2e52de5aa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 192 additions and 3 deletions

View File

@ -61,3 +61,9 @@
%feature("docstring", "Sets whether to JIT an expression if it cannot be interpreted."
) lldb::SBExpressionOptions::SetAllowJIT;
%feature("docstring", "Sets language-plugin specific boolean option for expression evaluation. LLDB currently doesn't validate whether the option being set is understood by the expression evaluator."
) lldb::SBExpressionOptions::SetBooleanLanguageOption;
%feature("docstring", "Gets language-plugin specific boolean option for expression evaluation. LLDB currently doesn't validate whether the option being retrieved is one that is understood by the expression evaluator."
) lldb::SBExpressionOptions::GetBooleanLanguageOption;

View File

@ -107,6 +107,10 @@ public:
// Sets whether we will JIT an expression if it cannot be interpreted
void SetAllowJIT(bool allow);
bool GetBooleanLanguageOption(const char *option_name, SBError &error) const;
SBError SetBooleanLanguageOption(const char *option_name, bool value);
protected:
lldb_private::EvaluateExpressionOptions *get() const;

View File

@ -38,6 +38,7 @@
#include "lldb/Utility/Broadcaster.h"
#include "lldb/Utility/LLDBAssert.h"
#include "lldb/Utility/RealpathPrefixes.h"
#include "lldb/Utility/StructuredData.h"
#include "lldb/Utility/Timeout.h"
#include "lldb/lldb-public.h"
#include "llvm/ADT/StringRef.h"
@ -307,6 +308,9 @@ private:
class EvaluateExpressionOptions {
public:
EvaluateExpressionOptions()
: m_language_options_sp(std::make_shared<StructuredData::Dictionary>()) {}
// MSVC has a bug here that reports C4268: 'const' static/global data
// initialized with compiler generated default constructor fills the object
// with zeros. Confirmed that MSVC is *not* zero-initializing, it's just a
@ -323,8 +327,6 @@ public:
static constexpr ExecutionPolicy default_execution_policy =
eExecutionPolicyOnlyWhenNeeded;
EvaluateExpressionOptions() = default;
ExecutionPolicy GetExecutionPolicy() const { return m_execution_policy; }
void SetExecutionPolicy(ExecutionPolicy policy = eExecutionPolicyAlways) {
@ -481,7 +483,22 @@ public:
void SetIsForUtilityExpr(bool b) { m_running_utility_expression = b; }
/// Set language-plugin specific option called \c option_name to
/// the specified boolean \c value.
llvm::Error SetBooleanLanguageOption(llvm::StringRef option_name, bool value);
/// Get the language-plugin specific boolean option called \c option_name.
///
/// If the option doesn't exist or is not a boolean option, returns false.
/// Otherwise returns the boolean value of the option.
llvm::Expected<bool>
GetBooleanLanguageOption(llvm::StringRef option_name) const;
private:
const StructuredData::Dictionary &GetLanguageOptions() const;
StructuredData::Dictionary &GetLanguageOptions();
ExecutionPolicy m_execution_policy = default_execution_policy;
SourceLanguage m_language;
std::string m_prefix;
@ -514,6 +531,10 @@ private:
mutable std::string m_pound_line_file;
mutable uint32_t m_pound_line_line = 0;
/// Dictionary mapping names of language-plugin specific options
/// to values.
StructuredData::DictionarySP m_language_options_sp = nullptr;
/// During expression evaluation, any SymbolContext in this list will be
/// used for symbol/function lookup before any other context (except for
/// the module corresponding to the current frame).

View File

@ -8,6 +8,7 @@
#include "lldb/API/SBExpressionOptions.h"
#include "Utils.h"
#include "lldb/API/SBError.h"
#include "lldb/API/SBStream.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/Instrumentation.h"
@ -256,6 +257,38 @@ void SBExpressionOptions::SetAllowJIT(bool allow) {
: eExecutionPolicyNever);
}
// FIXME: the language plugin should expression options dynamically and
// we should validate here (by asking the language plugin) that the options
// being set/retrieved are actually valid options.
bool SBExpressionOptions::GetBooleanLanguageOption(const char *option_name,
SBError &error) const {
LLDB_INSTRUMENT_VA(this, option_name, error);
error.Clear();
auto value_or_err = m_opaque_up->GetBooleanLanguageOption(option_name);
if (!value_or_err) {
error.SetErrorString(llvm::toString(value_or_err.takeError()).c_str());
return false;
}
return *value_or_err;
}
SBError SBExpressionOptions::SetBooleanLanguageOption(const char *option_name,
bool value) {
LLDB_INSTRUMENT_VA(this, option_name, value);
SBError error;
if (llvm::Error err =
m_opaque_up->SetBooleanLanguageOption(option_name, value))
error.SetErrorString(llvm::toString(std::move(err)).c_str());
return error;
}
EvaluateExpressionOptions *SBExpressionOptions::get() const {
return m_opaque_up.get();
}

View File

@ -70,6 +70,7 @@
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/Support/ErrorExtras.h"
#include "llvm/Support/ThreadPool.h"
#include <memory>
@ -5357,3 +5358,54 @@ void Target::NotifyBreakpointChanged(
if (EventTypeHasListeners(Target::eBroadcastBitBreakpointChanged))
BroadcastEvent(Target::eBroadcastBitBreakpointChanged, breakpoint_data_sp);
}
// FIXME: the language plugin should expression options dynamically and
// we should validate here (by asking the language plugin) that the options
// being set/retrieved are actually valid options.
llvm::Error
EvaluateExpressionOptions::SetBooleanLanguageOption(llvm::StringRef option_name,
bool value) {
if (option_name.empty())
return llvm::createStringError("Can't set an option with an empty name.");
if (StructuredData::ObjectSP existing_sp =
GetLanguageOptions().GetValueForKey(option_name);
existing_sp && existing_sp->GetType() != eStructuredDataTypeBoolean)
return llvm::createStringErrorV("Trying to override existing option '{0}' "
"of type '{1}' with a boolean value.",
option_name, existing_sp->GetType());
GetLanguageOptions().AddBooleanItem(option_name, value);
return llvm::Error::success();
}
llvm::Expected<bool> EvaluateExpressionOptions::GetBooleanLanguageOption(
llvm::StringRef option_name) const {
const StructuredData::Dictionary &opts = GetLanguageOptions();
if (!opts.HasKey(option_name))
return llvm::createStringErrorV("Option '{0}' does not exist.",
option_name);
bool result;
if (!opts.GetValueForKeyAsBoolean(option_name, result))
return llvm::createStringErrorV("Failed to get option '{0}' as boolean.",
option_name);
return result;
}
const StructuredData::Dictionary &
EvaluateExpressionOptions::GetLanguageOptions() const {
assert(m_language_options_sp);
return *m_language_options_sp;
}
StructuredData::Dictionary &EvaluateExpressionOptions::GetLanguageOptions() {
assert(m_language_options_sp);
return *m_language_options_sp;
}

View File

@ -7,7 +7,6 @@ o test_expr_options:
Test expression command options.
"""
import lldb
import lldbsuite.test.lldbutil as lldbutil
from lldbsuite.test.decorators import *
@ -85,3 +84,38 @@ class ExprOptionsTestCase(TestBase):
val = frame.EvaluateExpression("id == 0", options)
self.assertTrue(val.IsValid())
self.assertFalse(val.GetError().Success())
def test_expr_options_language_options(self):
"""Test SetBooleanLanguageOption/GetBooleanLanguageOption SBAPIs"""
error = lldb.SBError()
options = lldb.SBExpressionOptions()
self.assertFalse(options.GetBooleanLanguageOption("foo", error))
self.assertTrue(error.Fail())
self.assertFalse(options.GetBooleanLanguageOption("bar", error))
self.assertTrue(error.Fail())
self.assertTrue(options.SetBooleanLanguageOption("foo", True).Success())
self.assertTrue(options.SetBooleanLanguageOption("bar", True).Success())
self.assertTrue(options.GetBooleanLanguageOption("foo", error))
self.assertTrue(error.Success())
self.assertTrue(options.GetBooleanLanguageOption("bar", error))
self.assertTrue(error.Success())
self.assertTrue(options.SetBooleanLanguageOption("foo", False).Success())
self.assertTrue(options.SetBooleanLanguageOption("bar", False).Success())
self.assertFalse(options.GetBooleanLanguageOption("foo", error))
self.assertTrue(error.Success())
self.assertFalse(options.GetBooleanLanguageOption("bar", error))
self.assertTrue(error.Success())
self.assertFalse(options.GetBooleanLanguageOption("", error))
self.assertTrue(error.Fail())
self.assertTrue(options.SetBooleanLanguageOption("", True).Fail())
self.assertFalse(options.GetBooleanLanguageOption("", error))
self.assertTrue(error.Fail())
self.assertTrue(options.SetBooleanLanguageOption(None, True).Fail())
self.assertFalse(options.GetBooleanLanguageOption(None, error))
self.assertTrue(error.Fail())

View File

@ -11,6 +11,7 @@
#include "TestingSupport/TestUtilities.h"
#include "lldb/Expression/Expression.h"
#include "lldb/Target/Target.h"
#include "llvm/Testing/Support/Error.h"
using namespace lldb_private;
@ -127,3 +128,41 @@ TEST_P(ExpressionTestFixture, FunctionCallLabel) {
INSTANTIATE_TEST_SUITE_P(FunctionCallLabelTest, ExpressionTestFixture,
testing::ValuesIn(g_label_test_cases));
TEST(ExpressionTests, ExpressionOptions_Basic) {
EvaluateExpressionOptions options;
EXPECT_THAT_EXPECTED(options.GetBooleanLanguageOption("foo"),
llvm::FailedWithMessage("Option 'foo' does not exist."));
EXPECT_THAT_EXPECTED(options.GetBooleanLanguageOption("bar"),
llvm::FailedWithMessage("Option 'bar' does not exist."));
EXPECT_THAT_ERROR(options.SetBooleanLanguageOption("foo", true),
llvm::Succeeded());
EXPECT_THAT_ERROR(options.SetBooleanLanguageOption("bar", false),
llvm::Succeeded());
EXPECT_THAT_EXPECTED(options.GetBooleanLanguageOption("foo"),
llvm::HasValue(true));
EXPECT_THAT_EXPECTED(options.GetBooleanLanguageOption("bar"),
llvm::HasValue(false));
EXPECT_THAT_ERROR(options.SetBooleanLanguageOption("foo", false),
llvm::Succeeded());
EXPECT_THAT_ERROR(options.SetBooleanLanguageOption("bar", true),
llvm::Succeeded());
EXPECT_THAT_EXPECTED(options.GetBooleanLanguageOption("foo"),
llvm::HasValue(false));
EXPECT_THAT_EXPECTED(options.GetBooleanLanguageOption("bar"),
llvm::HasValue(true));
// Empty option names not allowed.
EXPECT_THAT_EXPECTED(options.GetBooleanLanguageOption(""),
llvm::FailedWithMessage("Option '' does not exist."));
EXPECT_THAT_ERROR(
options.SetBooleanLanguageOption("", true),
llvm::FailedWithMessage("Can't set an option with an empty name."));
EXPECT_THAT_EXPECTED(options.GetBooleanLanguageOption(""),
llvm::FailedWithMessage("Option '' does not exist."));
}