[lldb][CommandObjectType] Add --wants-dereference option to type synthetic add (#188512)

This patch exposes the `TypeSynthetic::SetFrontEndWantsDereference` via
the `type synthetic add` command.

The motivation for this is moving the various STL data-formatters to
Python. Those currently set this flag programmatically so that pointers
and references get formatted using the pointee synthetic provider.

Patch that makes use of this new option is:
https://github.com/llvm/llvm-project/pull/187677

Claude helped with writing the test code. Reviewed and cleaned it up
myself
This commit is contained in:
Michael Buch 2026-03-25 20:11:23 +00:00 committed by GitHub
parent 28318d5db8
commit b8b4804d17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 128 additions and 4 deletions

View File

@ -67,14 +67,16 @@ public:
bool m_skip_pointers;
bool m_skip_references;
bool m_cascade;
bool m_wants_deref;
FormatterMatchType m_match_type;
StringList m_target_types;
std::string m_category;
SynthAddOptions(bool sptr, bool sref, bool casc,
SynthAddOptions(bool sptr, bool sref, bool casc, bool wants_deref,
FormatterMatchType match_type, std::string catg)
: m_skip_pointers(sptr), m_skip_references(sref), m_cascade(casc),
m_match_type(match_type), m_category(catg) {}
m_wants_deref(wants_deref), m_match_type(match_type), m_category(catg) {
}
typedef std::shared_ptr<SynthAddOptions> SharedPointer;
};
@ -322,6 +324,13 @@ private:
error = Status::FromErrorStringWithFormat(
"invalid value for cascade: %s", option_arg.str().c_str());
break;
case 'D':
m_wants_deref = OptionArgParser::ToBoolean(option_arg, true, &success);
if (!success)
error = Status::FromErrorStringWithFormat(
"invalid value for wants-dereference: %s",
option_arg.str().c_str());
break;
case 'P':
handwrite_python = true;
break;
@ -361,6 +370,7 @@ private:
void OptionParsingStarting(ExecutionContext *execution_context) override {
m_cascade = true;
m_wants_deref = true;
m_class_name = "";
m_skip_pointers = false;
m_skip_references = false;
@ -379,6 +389,7 @@ private:
bool m_cascade;
bool m_skip_references;
bool m_skip_pointers;
bool m_wants_deref;
std::string m_class_name;
bool m_input_python;
std::string m_category;
@ -454,7 +465,8 @@ protected:
SyntheticChildren::Flags()
.SetCascades(options->m_cascade)
.SetSkipPointers(options->m_skip_pointers)
.SetSkipReferences(options->m_skip_references),
.SetSkipReferences(options->m_skip_references)
.SetFrontEndWantsDereference(options->m_wants_deref),
class_name_str.c_str());
lldb::TypeCategoryImplSP category;
@ -2131,7 +2143,8 @@ bool CommandObjectTypeSynthAdd::Execute_HandwritePython(
Args &command, CommandReturnObject &result) {
auto options = std::make_unique<SynthAddOptions>(
m_options.m_skip_pointers, m_options.m_skip_references,
m_options.m_cascade, m_options.m_match_type, m_options.m_category);
m_options.m_cascade, m_options.m_wants_deref, m_options.m_match_type,
m_options.m_category);
for (auto &entry : command.entries()) {
if (entry.ref().empty()) {
@ -2173,6 +2186,7 @@ bool CommandObjectTypeSynthAdd::Execute_PythonClass(
ScriptedSyntheticChildren *impl = new ScriptedSyntheticChildren(
SyntheticChildren::Flags()
.SetCascades(m_options.m_cascade)
.SetFrontEndWantsDereference(m_options.m_wants_deref)
.SetSkipPointers(m_options.m_skip_pointers)
.SetSkipReferences(m_options.m_skip_references),
m_options.m_class_name.c_str());

View File

@ -2267,6 +2267,12 @@ let Command = "type synth add" in {
def type_synth_add_cascade : Option<"cascade", "C">,
Arg<"Boolean">,
Desc<"If true, cascade through typedef chains.">;
def type_synth_add_wants_deref
: Option<"requires-dereference", "D">,
Arg<"Boolean">,
Desc<"If true, when this synthetic provider matches a pointer or "
"reference type, it will receive the dereferenced value object "
"instead of the raw pointer or reference.">;
def type_synth_add_skip_pointers
: Option<"skip-pointers", "p">,
Desc<"Don't use this format for pointers-to-type objects.">;

View File

@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@ -0,0 +1,52 @@
"""
Test the --requires-dereference flag of 'type synth add'.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TypeSynthRequiresDerefTestCase(TestBase):
def setUp(self):
TestBase.setUp(self)
self.addTearDownHook(lambda: self.runCmd("type synth clear", check=False))
def _setup_synthetic(self, requires_deref: bool):
self.runCmd("command script import provider.py")
self.runCmd(
f"type synth add -l provider.WrapperSynthProvider --requires-dereference {requires_deref} Wrapper"
)
def test_requires_deref_on_pointer(self):
"""With --requires-dereference true on pointer."""
self.build()
lldbutil.run_to_source_breakpoint(self, "return 0", lldb.SBFileSpec("main.cpp"))
self._setup_synthetic(True)
self.expect_var_path("wp", children=[ValueCheck(name="sum", value="30")])
def test_requires_deref_on_reference(self):
"""With --requires-dereference true on reference."""
self.build()
lldbutil.run_to_source_breakpoint(self, "return 0", lldb.SBFileSpec("main.cpp"))
self._setup_synthetic(True)
self.expect_var_path("wr", children=[ValueCheck(name="sum", value="30")])
def test_no_requires_deref_on_pointer(self):
"""With --requires-dereference false on pointer."""
self.build()
lldbutil.run_to_source_breakpoint(self, "return 0", lldb.SBFileSpec("main.cpp"))
self._setup_synthetic(False)
self.expect("frame variable wp", matching=False, substrs=["sum"])
def test_no_requires_deref_on_reference(self):
"""With --requires-dereference false on reference."""
self.build()
lldbutil.run_to_source_breakpoint(self, "return 0", lldb.SBFileSpec("main.cpp"))
self._setup_synthetic(False)
self.expect("frame variable wr", matching=False, substrs=["sum"])

View File

@ -0,0 +1,13 @@
struct Wrapper {
int x;
int y;
};
int main() {
Wrapper w{10, 20};
Wrapper *wp = &w;
Wrapper &wr = w;
(void)wp;
(void)wr;
return 0;
}

View File

@ -0,0 +1,36 @@
class WrapperSynthProvider:
def __init__(self, valobj, internal_dict):
self.valobj = valobj
self.sum_value = None
def update(self):
self.sum_value = None
ty = self.valobj.GetType()
# Artificially bail out if LLDB passed us a reference or a pointer.
if ty.IsPointerType() or ty.IsReferenceType():
return False
x = self.valobj.GetChildMemberWithName("x")
y = self.valobj.GetChildMemberWithName("y")
if x.IsValid() and y.IsValid():
sum_val = x.GetValueAsUnsigned(0) + y.GetValueAsUnsigned(0)
self.sum_value = self.valobj.CreateValueFromExpression("sum", str(sum_val))
return False
def num_children(self):
return 1
def get_child_at_index(self, index):
if index == 0 and self.sum_value:
return self.sum_value
return None
def get_child_index(self, name):
if name == "sum":
return 0
return -1
def has_children(self):
return True