[Lldb-commits] [lldb] 868186c - [lldb] Make callback-based formatter matching available from the CLI.
Jorge Gorbe Moya via lldb-commits
lldb-commits at lists.llvm.org
Thu Nov 10 10:33:55 PST 2022
Author: Jorge Gorbe Moya
Date: 2022-11-10T10:29:38-08:00
New Revision: 868186cf6cb7edba08c916f98f5f87a942bf50ba
URL: https://github.com/llvm/llvm-project/commit/868186cf6cb7edba08c916f98f5f87a942bf50ba
DIFF: https://github.com/llvm/llvm-project/commit/868186cf6cb7edba08c916f98f5f87a942bf50ba.diff
LOG: [lldb] Make callback-based formatter matching available from the CLI.
This change adds a `--recognizer-function` (`-R`) to `type summary add`
and `type synth add` that allows users to specify that the names in
the command are not type names but python function names.
It also adds an example to lldb/examples, and a section in the data
formatters documentation on how to use recognizer functions.
Differential Revision: https://reviews.llvm.org/D137000
Added:
lldb/examples/synthetic/recognizer_function/example.py
lldb/examples/synthetic/recognizer_function/lldb-commands
lldb/examples/synthetic/recognizer_function/program.cpp
Modified:
lldb/docs/use/variable.rst
lldb/source/Commands/CommandObjectType.cpp
lldb/source/Commands/Options.td
lldb/test/API/functionalities/data-formatter/callback-matching/TestDataFormatterCallbackMatching.py
lldb/test/API/functionalities/data-formatter/callback-matching/formatters_with_callback.py
Removed:
################################################################################
diff --git a/lldb/docs/use/variable.rst b/lldb/docs/use/variable.rst
index e86b863f24856..1afe7c28b6b7e 100644
--- a/lldb/docs/use/variable.rst
+++ b/lldb/docs/use/variable.rst
@@ -1109,6 +1109,39 @@ only need to see the ones named B, H and Q, you can define a filter:
(std::string) Q = "Hello world"
}
+Callback-based type matching
+----------------------------
+
+Even though regular expression matching works well for the vast majority of data
+formatters (you normally know the name of the type you're writing a formatter
+for), there are some cases where it's useful to look at the type before deciding
+what formatter to apply.
+
+As an example scenario, imagine we have a code generator that produces some
+classes that inherit from a common ``GeneratedObject`` class, and we have a
+summary function and a synthetic child provider that work for all
+``GeneratedObject`` instances (they all follow the same pattern). However, there
+is no common pattern in the name of these classes, so we can't register the
+formatter neither by name nor by regular expression.
+
+In that case, you can write a recognizer function like this:
+
+::
+
+ def is_generated_object(sbtype, internal_dict):
+ for base in sbtype.get_bases_array():
+ if base.GetName() == "GeneratedObject"
+ return True
+ return False
+
+And pass this function to ``type summary add`` and ``type synthetic add`` using
+the flag ``--recognizer-function``.
+
+::
+
+ (lldb) type summary add --expand --python-function my_summary_function --recognizer-function is_generated_object
+ (lldb) type synthetic add --python-class my_child_provider --recognizer-function is_generated_object
+
Objective-C Dynamic Type Discovery
----------------------------------
diff --git a/lldb/examples/synthetic/recognizer_function/example.py b/lldb/examples/synthetic/recognizer_function/example.py
new file mode 100644
index 0000000000000..f2d4f75d50e8b
--- /dev/null
+++ b/lldb/examples/synthetic/recognizer_function/example.py
@@ -0,0 +1,63 @@
+# Formatters for classes that derive from Message.
+#
+# Usage:
+# command script import ./example.py
+# type summary add --expand --recognizer-function --python-function example.message_summary example.is_message_type
+# type synth add --recognizer-function --python-class example.MessageChildProvider example.is_message_type
+
+import sys
+
+def is_message_type(t, internal_dict):
+ for base in t.get_bases_array():
+ if base.GetName() == "Message":
+ return True
+ return False
+
+def message_summary(value, internal_dict):
+ # Could have used a summary string as well. All the work is done by the child
+ # provider.
+ return "Message"
+
+class MessageChildProvider:
+ def __init__(self, value, internal_dict):
+ self.value = value
+ self.synthetic_children = self._analyze_children(value)
+
+ def has_children(self):
+ return self.num_children() > 0
+
+ def num_children(self):
+ return len(self.synthetic_children)
+
+ def get_child_index(self, name):
+ for index, child in enumerate(self.synthetic_children):
+ if child.GetName() == name:
+ return index
+ return None
+
+ def get_child_at_index(self, index):
+ return self.synthetic_children[index]
+
+ def _rename_sbvalue(self, value):
+ # We want to display the field with its original name without a trailing
+ # underscore. So we create a new SBValue with the same type and address but
+ # a
diff erent name.
+ name = value.GetName()
+ assert name.endswith("_")
+ new_name = name[:-1]
+ return value.CreateValueFromAddress(new_name, value.GetLoadAddress(),
+ value.GetType())
+
+ def _analyze_children(self, value):
+ result = []
+ for i in range(value.GetNumChildren()):
+ child = value.GetChildAtIndex(i)
+ child_name = child.GetName()
+ if child_name.startswith("_"):
+ continue # Internal field, skip
+ # Normal field. Check presence bit.
+ presence_bit = value.GetChildMemberWithName("_has_" + child_name)
+ if presence_bit.GetValueAsUnsigned() != 0:
+ result.append(self._rename_sbvalue(child))
+ return result
+
diff --git a/lldb/examples/synthetic/recognizer_function/lldb-commands b/lldb/examples/synthetic/recognizer_function/lldb-commands
new file mode 100644
index 0000000000000..d45d8be5f4702
--- /dev/null
+++ b/lldb/examples/synthetic/recognizer_function/lldb-commands
@@ -0,0 +1,7 @@
+command script import ./example.py
+type summary add --expand --recognizer-function --python-function example.message_summary example.is_message_type
+type synth add --recognizer-function --python-class example.MessageChildProvider example.is_message_type
+b program.cpp:112
+r
+p customer
+p order
diff --git a/lldb/examples/synthetic/recognizer_function/program.cpp b/lldb/examples/synthetic/recognizer_function/program.cpp
new file mode 100644
index 0000000000000..826725573656d
--- /dev/null
+++ b/lldb/examples/synthetic/recognizer_function/program.cpp
@@ -0,0 +1,114 @@
+// Example program for matching summary functions and synthetic child providers.
+//
+// The classes here simulate code generated by a serialization tool like, for
+// example, protocol buffers. But the actual "generated" class layout is
+// extremely naive to simplify the example.
+//
+// The idea is that we want to have generic formatters for a bunch of message
+// classes, because they are all generated following common patterns, but the
+// matching can't be based in the type name, because it can be anything.
+
+#include <string>
+
+class Message {
+ // Dummy method definitions to illustrate a possible generic message API.
+ std::string serialize() { return "TODO"; }
+ Message* deserialize() {
+ return nullptr; // TODO.
+ }
+};
+
+// This class could have been generated from a description like this. Assume
+// fields are always optional, for simplicity (e.g. we don't care during
+// serialization if a Customer has a name or not, we're just moving data around
+// and validation happens elsewhere).
+//
+// message Customer {
+// string name;
+// int age;
+// string address;
+// }
+class Customer : public Message {
+ private:
+ int _internal_bookkeeping_bits_;
+
+ // Presence bits. They are true if the field has been set.
+ bool _has_name_ = false;
+ bool _has_age_ = false;
+ bool _has_address_ = false;
+
+ // Actual field data.
+ std::string name_;
+ int age_;
+ std::string address_;
+
+ public:
+ // Getters and setters.
+ bool has_name() { return _has_name_; }
+ bool has_age() { return _has_age_; }
+ bool has_address() { return _has_address_; }
+
+ std::string name() { return name_; }
+ int age() { return age_; }
+ std::string address() { return address_; }
+
+ void set_name(std::string name) {
+ name_ = name;
+ _has_name_ = true;
+ }
+ void set_age(int age) {
+ age_ = age;
+ _has_age_ = true;
+ }
+ void set_address(std::string address) {
+ address_ = address;
+ _has_address_ = true;
+ }
+};
+
+// message ProductOrder {
+// string product_name;
+// int amount;
+// }
+class ProductOrder : public Message {
+ private:
+ int _internal_bookkeeping_bits_;
+
+ // Presence bits. They are true if the field has been set.
+ bool _has_product_name_ = false;
+ bool _has_amount_ = false;
+
+ // Actual field data.
+ std::string product_name_;
+ int amount_;
+
+ public:
+ // Getters and setters.
+ bool has_product_name() { return _has_product_name_; }
+ bool has_amount() { return _has_amount_; }
+
+ std::string get_product_name() { return product_name_; }
+ int get_amount() { return amount_; }
+
+ void set_product_name(std::string product_name) {
+ product_name_ = product_name;
+ _has_product_name_ = true;
+ }
+ void set_amount(int amount) {
+ amount_ = amount;
+ _has_amount_ = true;
+ }
+};
+
+int main(int argc, char **argv) {
+ Customer customer;
+ customer.set_name("C. Ustomer");
+ customer.set_address("123 Fake St.");
+ // no age, so we can check absent fields get omitted.
+
+ ProductOrder order;
+ order.set_product_name("widget");
+ order.set_amount(100);
+ return 0; // break here.
+}
+
diff --git a/lldb/source/Commands/CommandObjectType.cpp b/lldb/source/Commands/CommandObjectType.cpp
index ccbe7922e65fe..6f09deae6a408 100644
--- a/lldb/source/Commands/CommandObjectType.cpp
+++ b/lldb/source/Commands/CommandObjectType.cpp
@@ -49,13 +49,15 @@ class ScriptAddOptions {
public:
TypeSummaryImpl::Flags m_flags;
StringList m_target_types;
- bool m_regex;
+ FormatterMatchType m_match_type;
ConstString m_name;
std::string m_category;
- ScriptAddOptions(const TypeSummaryImpl::Flags &flags, bool regx,
- ConstString name, std::string catg)
- : m_flags(flags), m_regex(regx), m_name(name), m_category(catg) {}
+ ScriptAddOptions(const TypeSummaryImpl::Flags &flags,
+ FormatterMatchType match_type, ConstString name,
+ std::string catg)
+ : m_flags(flags), m_match_type(match_type), m_name(name),
+ m_category(catg) {}
typedef std::shared_ptr<ScriptAddOptions> SharedPointer;
};
@@ -65,13 +67,14 @@ class SynthAddOptions {
bool m_skip_pointers;
bool m_skip_references;
bool m_cascade;
- bool m_regex;
+ FormatterMatchType m_match_type;
StringList m_target_types;
std::string m_category;
- SynthAddOptions(bool sptr, bool sref, bool casc, bool regx, std::string catg)
+ SynthAddOptions(bool sptr, bool sref, bool casc,
+ FormatterMatchType match_type, std::string catg)
: m_skip_pointers(sptr), m_skip_references(sref), m_cascade(casc),
- m_regex(regx), m_category(catg) {}
+ m_match_type(match_type), m_category(catg) {}
typedef std::shared_ptr<SynthAddOptions> SharedPointer;
};
@@ -121,7 +124,7 @@ class CommandObjectTypeSummaryAdd : public CommandObjectParsed,
// Instance variables to hold the values for command options.
TypeSummaryImpl::Flags m_flags;
- bool m_regex = false;
+ FormatterMatchType m_match_type = eFormatterMatchExact;
std::string m_format_string;
ConstString m_name;
std::string m_python_script;
@@ -139,8 +142,6 @@ class CommandObjectTypeSummaryAdd : public CommandObjectParsed,
bool Execute_StringSummary(Args &command, CommandReturnObject &result);
public:
- enum SummaryFormatType { eRegularSummary, eRegexSummary, eNamedSummary };
-
CommandObjectTypeSummaryAdd(CommandInterpreter &interpreter);
~CommandObjectTypeSummaryAdd() override = default;
@@ -198,12 +199,9 @@ class CommandObjectTypeSummaryAdd : public CommandObjectParsed,
Status error;
for (const std::string &type_name : options->m_target_types) {
- CommandObjectTypeSummaryAdd::AddSummary(
- ConstString(type_name), script_format,
- (options->m_regex
- ? CommandObjectTypeSummaryAdd::eRegexSummary
- : CommandObjectTypeSummaryAdd::eRegularSummary),
- options->m_category, &error);
+ AddSummary(ConstString(type_name), script_format,
+ options->m_match_type, options->m_category,
+ &error);
if (error.Fail()) {
error_sp->Printf("error: %s", error.AsCString());
error_sp->Flush();
@@ -211,15 +209,11 @@ class CommandObjectTypeSummaryAdd : public CommandObjectParsed,
}
if (options->m_name) {
- CommandObjectTypeSummaryAdd::AddSummary(
- options->m_name, script_format,
- CommandObjectTypeSummaryAdd::eNamedSummary,
- options->m_category, &error);
+ CommandObjectTypeSummaryAdd::AddNamedSummary(
+ options->m_name, script_format, &error);
if (error.Fail()) {
- CommandObjectTypeSummaryAdd::AddSummary(
- options->m_name, script_format,
- CommandObjectTypeSummaryAdd::eNamedSummary,
- options->m_category, &error);
+ CommandObjectTypeSummaryAdd::AddNamedSummary(
+ options->m_name, script_format, &error);
if (error.Fail()) {
error_sp->Printf("error: %s", error.AsCString());
error_sp->Flush();
@@ -261,9 +255,12 @@ class CommandObjectTypeSummaryAdd : public CommandObjectParsed,
io_handler.SetIsDone(true);
}
- static bool AddSummary(ConstString type_name, lldb::TypeSummaryImplSP entry,
- SummaryFormatType type, std::string category,
- Status *error = nullptr);
+ bool AddSummary(ConstString type_name, lldb::TypeSummaryImplSP entry,
+ FormatterMatchType match_type, std::string category,
+ Status *error = nullptr);
+
+ bool AddNamedSummary(ConstString summary_name, lldb::TypeSummaryImplSP entry,
+ Status *error = nullptr);
protected:
bool DoExecute(Args &command, CommandReturnObject &result) override;
@@ -322,7 +319,18 @@ class CommandObjectTypeSynthAdd : public CommandObjectParsed,
m_category = std::string(option_arg);
break;
case 'x':
- m_regex = true;
+ if (m_match_type == eFormatterMatchCallback)
+ error.SetErrorString(
+ "can't use --regex and --recognizer-function at the same time");
+ else
+ m_match_type = eFormatterMatchRegex;
+ break;
+ case '\x01':
+ if (m_match_type == eFormatterMatchRegex)
+ error.SetErrorString(
+ "can't use --regex and --recognizer-function at the same time");
+ else
+ m_match_type = eFormatterMatchCallback;
break;
default:
llvm_unreachable("Unimplemented option");
@@ -339,7 +347,7 @@ class CommandObjectTypeSynthAdd : public CommandObjectParsed,
m_category = "default";
is_class_based = false;
handwrite_python = false;
- m_regex = false;
+ m_match_type = eFormatterMatchExact;
}
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
@@ -356,7 +364,7 @@ class CommandObjectTypeSynthAdd : public CommandObjectParsed,
std::string m_category;
bool is_class_based;
bool handwrite_python;
- bool m_regex;
+ FormatterMatchType m_match_type;
};
CommandOptions m_options;
@@ -436,12 +444,9 @@ class CommandObjectTypeSynthAdd : public CommandObjectParsed,
for (const std::string &type_name : options->m_target_types) {
if (!type_name.empty()) {
- if (!CommandObjectTypeSynthAdd::AddSynth(
- ConstString(type_name), synth_provider,
- options->m_regex
- ? CommandObjectTypeSynthAdd::eRegexSynth
- : CommandObjectTypeSynthAdd::eRegularSynth,
- options->m_category, &error)) {
+ if (AddSynth(ConstString(type_name), synth_provider,
+ options->m_match_type, options->m_category,
+ &error)) {
error_sp->Printf("error: %s\n", error.AsCString());
error_sp->Flush();
break;
@@ -480,15 +485,13 @@ class CommandObjectTypeSynthAdd : public CommandObjectParsed,
}
public:
- enum SynthFormatType { eRegularSynth, eRegexSynth };
-
CommandObjectTypeSynthAdd(CommandInterpreter &interpreter);
~CommandObjectTypeSynthAdd() override = default;
- static bool AddSynth(ConstString type_name, lldb::SyntheticChildrenSP entry,
- SynthFormatType type, std::string category_name,
- Status *error);
+ bool AddSynth(ConstString type_name, lldb::SyntheticChildrenSP entry,
+ FormatterMatchType match_type, std::string category_name,
+ Status *error);
};
// CommandObjectTypeFormatAdd
@@ -1167,7 +1170,18 @@ Status CommandObjectTypeSummaryAdd::CommandOptions::SetOptionValue(
m_flags.SetSkipReferences(true);
break;
case 'x':
- m_regex = true;
+ if (m_match_type == eFormatterMatchCallback)
+ error.SetErrorString(
+ "can't use --regex and --recognizer-function at the same time");
+ else
+ m_match_type = eFormatterMatchRegex;
+ break;
+ case '\x01':
+ if (m_match_type == eFormatterMatchRegex)
+ error.SetErrorString(
+ "can't use --regex and --recognizer-function at the same time");
+ else
+ m_match_type = eFormatterMatchCallback;
break;
case 'n':
m_name.SetString(option_arg);
@@ -1204,7 +1218,7 @@ void CommandObjectTypeSummaryAdd::CommandOptions::OptionParsingStarting(
.SetSkipReferences(false)
.SetHideItemNames(false);
- m_regex = false;
+ m_match_type = eFormatterMatchExact;
m_name.Clear();
m_python_script = "";
m_python_function = "";
@@ -1278,7 +1292,7 @@ bool CommandObjectTypeSummaryAdd::Execute_ScriptSummary(
} else {
// Use an IOHandler to grab Python code from the user
auto options = std::make_unique<ScriptAddOptions>(
- m_options.m_flags, m_options.m_regex, m_options.m_name,
+ m_options.m_flags, m_options.m_match_type, m_options.m_name,
m_options.m_category);
for (auto &entry : command.entries()) {
@@ -1306,10 +1320,8 @@ bool CommandObjectTypeSummaryAdd::Execute_ScriptSummary(
Status error;
for (auto &entry : command.entries()) {
- CommandObjectTypeSummaryAdd::AddSummary(
- ConstString(entry.ref()), script_format,
- (m_options.m_regex ? eRegexSummary : eRegularSummary),
- m_options.m_category, &error);
+ AddSummary(ConstString(entry.ref()), script_format, m_options.m_match_type,
+ m_options.m_category, &error);
if (error.Fail()) {
result.AppendError(error.AsCString());
return false;
@@ -1317,8 +1329,7 @@ bool CommandObjectTypeSummaryAdd::Execute_ScriptSummary(
}
if (m_options.m_name) {
- AddSummary(m_options.m_name, script_format, eNamedSummary,
- m_options.m_category, &error);
+ AddNamedSummary(m_options.m_name, script_format, &error);
if (error.Fail()) {
result.AppendError(error.AsCString());
result.AppendError("added to types, but not given a name");
@@ -1379,9 +1390,8 @@ bool CommandObjectTypeSummaryAdd::Execute_StringSummary(
}
ConstString typeCS(arg_entry.ref());
- AddSummary(typeCS, entry,
- (m_options.m_regex ? eRegexSummary : eRegularSummary),
- m_options.m_category, &error);
+ AddSummary(typeCS, entry, m_options.m_match_type, m_options.m_category,
+ &error);
if (error.Fail()) {
result.AppendError(error.AsCString());
@@ -1390,8 +1400,7 @@ bool CommandObjectTypeSummaryAdd::Execute_StringSummary(
}
if (m_options.m_name) {
- AddSummary(m_options.m_name, entry, eNamedSummary, m_options.m_category,
- &error);
+ AddNamedSummary(m_options.m_name, entry, &error);
if (error.Fail()) {
result.AppendError(error.AsCString());
result.AppendError("added to types, but not given a name");
@@ -1546,31 +1555,29 @@ static bool FixArrayTypeNameWithRegex(ConstString &type_name) {
return false;
}
+bool CommandObjectTypeSummaryAdd::AddNamedSummary(ConstString summary_name,
+ TypeSummaryImplSP entry,
+ Status *error) {
+ // system named summaries do not exist (yet?)
+ DataVisualization::NamedSummaryFormats::Add(summary_name, entry);
+ return true;
+}
+
bool CommandObjectTypeSummaryAdd::AddSummary(ConstString type_name,
TypeSummaryImplSP entry,
- SummaryFormatType type,
+ FormatterMatchType match_type,
std::string category_name,
Status *error) {
-
- // Named summaries are a special case, they exist in their own map in the
- // FormatManager, outside of any categories.
- if (type == eNamedSummary) {
- // system named summaries do not exist (yet?)
- DataVisualization::NamedSummaryFormats::Add(type_name, entry);
- return true;
- }
-
lldb::TypeCategoryImplSP category;
DataVisualization::Categories::GetCategory(ConstString(category_name.c_str()),
category);
- if (type == eRegularSummary) {
+ if (match_type == eFormatterMatchExact) {
if (FixArrayTypeNameWithRegex(type_name))
- type = eRegexSummary;
+ match_type = eFormatterMatchRegex;
}
- FormatterMatchType match_type = eFormatterMatchExact;
- if (type == eRegexSummary) {
+ if (match_type == eFormatterMatchRegex) {
match_type = eFormatterMatchRegex;
RegularExpression typeRX(type_name.GetStringRef());
if (!typeRX.IsValid()) {
@@ -1580,6 +1587,18 @@ bool CommandObjectTypeSummaryAdd::AddSummary(ConstString type_name,
return false;
}
}
+
+ if (match_type == eFormatterMatchCallback) {
+ const char *function_name = type_name.AsCString();
+ ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter();
+ if (interpreter && !interpreter->CheckObjectExists(function_name)) {
+ error->SetErrorStringWithFormat(
+ "The provided recognizer function \"%s\" does not exist - "
+ "please define it before attempting to use this summary.\n",
+ function_name);
+ return false;
+ }
+ }
category->AddTypeSummary(type_name.GetStringRef(), match_type, entry);
return true;
}
@@ -2187,7 +2206,7 @@ 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_regex, m_options.m_category);
+ m_options.m_cascade, m_options.m_match_type, m_options.m_category);
for (auto &entry : command.entries()) {
if (entry.ref().empty()) {
@@ -2257,9 +2276,8 @@ bool CommandObjectTypeSynthAdd::Execute_PythonClass(
}
ConstString typeCS(arg_entry.ref());
- if (!AddSynth(typeCS, entry,
- m_options.m_regex ? eRegexSynth : eRegularSynth,
- m_options.m_category, &error)) {
+ if (!AddSynth(typeCS, entry, m_options.m_match_type, m_options.m_category,
+ &error)) {
result.AppendError(error.AsCString());
return false;
}
@@ -2287,22 +2305,22 @@ CommandObjectTypeSynthAdd::CommandObjectTypeSynthAdd(
bool CommandObjectTypeSynthAdd::AddSynth(ConstString type_name,
SyntheticChildrenSP entry,
- SynthFormatType type,
+ FormatterMatchType match_type,
std::string category_name,
Status *error) {
lldb::TypeCategoryImplSP category;
DataVisualization::Categories::GetCategory(ConstString(category_name.c_str()),
category);
- if (type == eRegularSynth) {
+ if (match_type == eFormatterMatchExact) {
if (FixArrayTypeNameWithRegex(type_name))
- type = eRegexSynth;
+ match_type = eFormatterMatchRegex;
}
// Only check for conflicting filters in the same category if `type_name` is
// an actual type name. Matching a regex string against registered regexes
// doesn't work.
- if (type == eRegularSynth) {
+ if (match_type == eFormatterMatchExact) {
// It's not generally possible to get a type object here. For example, this
// command can be run before loading any binaries. Do just a best-effort
// name-based lookup here to try to prevent conflicts.
@@ -2318,9 +2336,7 @@ bool CommandObjectTypeSynthAdd::AddSynth(ConstString type_name,
}
}
- FormatterMatchType match_type = eFormatterMatchExact;
- if (type == eRegexSynth) {
- match_type = eFormatterMatchRegex;
+ if (match_type == eFormatterMatchRegex) {
RegularExpression typeRX(type_name.GetStringRef());
if (!typeRX.IsValid()) {
if (error)
@@ -2330,6 +2346,18 @@ bool CommandObjectTypeSynthAdd::AddSynth(ConstString type_name,
}
}
+ if (match_type == eFormatterMatchCallback) {
+ const char *function_name = type_name.AsCString();
+ ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter();
+ if (interpreter && !interpreter->CheckObjectExists(function_name)) {
+ error->SetErrorStringWithFormat(
+ "The provided recognizer function \"%s\" does not exist - "
+ "please define it before attempting to use this summary.\n",
+ function_name);
+ return false;
+ }
+ }
+
category->AddTypeSynthetic(type_name.GetStringRef(), match_type, entry);
return true;
}
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index b4517877f91c9..6aee2ac228049 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1189,6 +1189,11 @@ let Command = "type summary add" in {
Desc<"Don't use this format for references-to-type objects.">;
def type_summary_add_regex : Option<"regex", "x">,
Desc<"Type names are actually regular expressions.">;
+ def type_summary_add_recognizer_function :
+ Option<"recognizer-function", "\\x01">,
+ Desc<"The names in the argument list are actually the names of python "
+ "functions that decide whether to use this summary for any given type. "
+ "Cannot be specified at the same time as --regex (-x).">;
def type_summary_add_inline_children : Option<"inline-children", "c">,
Group<1>, Required,
Desc<"If true, inline all child values into summary string.">;
@@ -1230,6 +1235,11 @@ let Command = "type synth add" in {
"children.">;
def type_synth_add_regex : Option<"regex", "x">,
Desc<"Type names are actually regular expressions.">;
+ def type_synth_add_recognizer_function :
+ Option<"recognizer-function", "\\x01">,
+ Desc<"The names in the argument list are actually the names of python "
+ "functions that decide whether to use this summary for any given type. "
+ "Cannot be specified at the same time as --regex (-x).">;
}
let Command = "type format add" in {
diff --git a/lldb/test/API/functionalities/data-formatter/callback-matching/TestDataFormatterCallbackMatching.py b/lldb/test/API/functionalities/data-formatter/callback-matching/TestDataFormatterCallbackMatching.py
index 91403d6d5f163..f4487d6338872 100644
--- a/lldb/test/API/functionalities/data-formatter/callback-matching/TestDataFormatterCallbackMatching.py
+++ b/lldb/test/API/functionalities/data-formatter/callback-matching/TestDataFormatterCallbackMatching.py
@@ -16,7 +16,7 @@ def setUp(self):
# Find the line number to break at.
self.line = line_number('main.cpp', '// Set break point at this line.')
- def test_callback_matchers(self):
+ def test_callback_matchers_api_registration(self):
"""Test data formatter commands."""
self.build()
@@ -31,6 +31,7 @@ def test_callback_matchers(self):
# now set up a summary function that uses a python callback to match
# classes that derive from `Base`.
self.runCmd("command script import --allow-reload ./formatters_with_callback.py")
+ self.runCmd("script formatters_with_callback.register_formatters(lldb.debugger)")
# Now `derived` should use our callback summary + synthetic children.
self.expect("frame variable derived",
@@ -47,3 +48,40 @@ def test_callback_matchers(self):
substrs=['hello from callback summary'])
self.expect("frame variable nd",
substrs=['z = 4444'])
+
+ def test_callback_matchers_cli_registration(self):
+ """Test data formatter commands."""
+ self.build()
+
+ _, process, thread, _ = lldbutil.run_to_line_breakpoint(
+ self, lldb.SBFileSpec("main.cpp"), self.line)
+
+ # Print derived without a formatter.
+ self.expect("frame variable derived",
+ substrs=['x = 2222',
+ 'y = 3333'])
+
+ # now set up a summary function that uses a python callback to match
+ # classes that derive from `Base`.
+ self.runCmd("command script import --allow-reload ./formatters_with_callback.py")
+ self.runCmd("type summary add -e -s 'hello from callback summary' "
+ "--recognizer-function formatters_with_callback.derives_from_base")
+ self.runCmd("type synth add -l formatters_with_callback.SynthProvider "
+ "--recognizer-function formatters_with_callback.derives_from_base")
+
+ # Now `derived` should use our callback summary + synthetic children.
+ self.expect("frame variable derived",
+ substrs=['hello from callback summary',
+ 'synthetic_child = 9999'])
+
+ # But not other classes.
+ self.expect("frame variable base", matching=False,
+ substrs=['hello from callback summary'])
+ self.expect("frame variable base",
+ substrs=['x = 1111'])
+
+ self.expect("frame variable nd", matching=False,
+ substrs=['hello from callback summary'])
+ self.expect("frame variable nd",
+ substrs=['z = 4444'])
+
diff --git a/lldb/test/API/functionalities/data-formatter/callback-matching/formatters_with_callback.py b/lldb/test/API/functionalities/data-formatter/callback-matching/formatters_with_callback.py
index 60e919a94352e..f040f2348d4de 100644
--- a/lldb/test/API/functionalities/data-formatter/callback-matching/formatters_with_callback.py
+++ b/lldb/test/API/functionalities/data-formatter/callback-matching/formatters_with_callback.py
@@ -24,7 +24,7 @@ def get_child_at_index(self, index):
return None
-def __lldb_init_module(debugger, dict):
+def register_formatters(debugger):
cat = debugger.CreateCategory("callback_formatters")
cat.AddTypeSummary(
lldb.SBTypeNameSpecifier("formatters_with_callback.derives_from_base",
More information about the lldb-commits
mailing list