Skip to content

Commit

Permalink
[lldb] Add matching based on Python callbacks for data formatters.
Browse files Browse the repository at this point in the history
This patch adds a new matching method for data formatters, in addition
to the existing exact typename and regex-based matching. The new method
allows users to specify the name of a Python callback function that
takes a `SBType` object and decides whether the type is a match or not.

Here is an overview of the changes performed:

- Add a new `eFormatterMatchCallback` matching type, and logic to handle
  it in `TypeMatcher` and `SBTypeNameSpecifier`.

- Extend `FormattersMatchCandidate` instances with a pointer to the
  current `ScriptInterpreter` and the `TypeImpl` corresponding to the
  candidate type, so we can run registered callbacks and pass the type
  to them. All matcher search functions now receive a
  `FormattersMatchCandidate` instead of a type name.

- Add some glue code to ScriptInterpreterPython and the SWIG bindings to
  allow calling a formatter matching callback. Most of this code is
  modeled after the equivalent code for watchpoint callback functions.

- Add an API test for the new callback-based matching feature.

For more context, please check the RFC thread where this feature was
originally discussed:
https://discourse.llvm.org/t/rfc-python-callback-for-data-formatters-type-matching/64204/11

Differential Revision: https://reviews.llvm.org/D135648
  • Loading branch information
slackito committed Oct 19, 2022
1 parent b8b740c commit d765664
Show file tree
Hide file tree
Showing 30 changed files with 342 additions and 103 deletions.
4 changes: 4 additions & 0 deletions lldb/bindings/python/python-swigsafecast.swig
Expand Up @@ -97,6 +97,10 @@ PythonObject ToSWIGWrapper(lldb::ExecutionContextRefSP ctx_sp) {
SWIGTYPE_p_lldb__SBExecutionContext);
}

PythonObject ToSWIGWrapper(lldb::TypeImplSP type_impl_sp) {
return ToSWIGHelper(new lldb::SBType(type_impl_sp), SWIGTYPE_p_lldb__SBType);
}

PythonObject ToSWIGWrapper(const TypeSummaryOptions &summary_options) {
return ToSWIGHelper(new lldb::SBTypeSummaryOptions(summary_options),
SWIGTYPE_p_lldb__SBTypeSummaryOptions);
Expand Down
26 changes: 26 additions & 0 deletions lldb/bindings/python/python-wrapper.swig
Expand Up @@ -90,6 +90,32 @@ bool lldb_private::LLDBSwigPythonWatchpointCallbackFunction(
return stop_at_watchpoint;
}

// This function is called by
// ScriptInterpreterPython::FormatterMatchingCallbackFunction and it's used when
// a data formatter provides the name of a callback to inspect a candidate type
// before considering a match.
bool lldb_private::LLDBSwigPythonFormatterCallbackFunction(
const char *python_function_name, const char *session_dictionary_name,
lldb::TypeImplSP type_impl_sp) {

PyErr_Cleaner py_err_cleaner(true);

auto dict = PythonModule::MainModule().ResolveName<PythonDictionary>(
session_dictionary_name);
auto pfunc = PythonObject::ResolveNameWithDictionary<PythonCallable>(
python_function_name, dict);

if (!pfunc.IsAllocated())
return false;

PythonObject result =
pfunc(ToSWIGWrapper(type_impl_sp), dict);

// Only if everything goes okay and the function returns True we'll consider
// it a match.
return result.get() == Py_True;
}

bool lldb_private::LLDBSwigPythonCallTypeScript(
const char *python_function_name, const void *session_dictionary,
const lldb::ValueObjectSP &valobj_sp, void **pyfunct_wrapper,
Expand Down
2 changes: 1 addition & 1 deletion lldb/include/lldb/API/SBType.h
Expand Up @@ -106,6 +106,7 @@ class SBType {
SBType();

SBType(const lldb::SBType &rhs);
SBType(const lldb::TypeImplSP &);

~SBType();

Expand Down Expand Up @@ -239,7 +240,6 @@ class SBType {

SBType(const lldb_private::CompilerType &);
SBType(const lldb::TypeSP &);
SBType(const lldb::TypeImplSP &);
};

class SBTypeList {
Expand Down
2 changes: 1 addition & 1 deletion lldb/include/lldb/DataFormatters/DataVisualization.h
Expand Up @@ -51,7 +51,7 @@ class DataVisualization {
GetSyntheticChildren(ValueObject &valobj, lldb::DynamicValueType use_dynamic);

static bool
AnyMatches(ConstString type_name,
AnyMatches(const FormattersMatchCandidate &candidate_type,
TypeCategoryImpl::FormatCategoryItems items =
TypeCategoryImpl::ALL_ITEM_TYPES,
bool only_enabled = true, const char **matching_category = nullptr,
Expand Down
18 changes: 16 additions & 2 deletions lldb/include/lldb/DataFormatters/FormatClasses.h
Expand Up @@ -17,6 +17,7 @@
#include "lldb/DataFormatters/TypeFormat.h"
#include "lldb/DataFormatters/TypeSummary.h"
#include "lldb/DataFormatters/TypeSynthetic.h"
#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/Symbol/CompilerType.h"
#include "lldb/Symbol/Type.h"
#include "lldb/lldb-enumerations.h"
Expand Down Expand Up @@ -73,13 +74,22 @@ class FormattersMatchCandidate {
}
};

FormattersMatchCandidate(ConstString name, Flags flags)
: m_type_name(name), m_flags(flags) {}
FormattersMatchCandidate(ConstString name,
ScriptInterpreter *script_interpreter, TypeImpl type,
Flags flags)
: m_type_name(name), m_script_interpreter(script_interpreter),
m_type(type), m_flags(flags) {}

~FormattersMatchCandidate() = default;

ConstString GetTypeName() const { return m_type_name; }

TypeImpl GetType() const { return m_type; }

ScriptInterpreter *GetScriptInterpreter() const {
return m_script_interpreter;
}

bool DidStripPointer() const { return m_flags.stripped_pointer; }

bool DidStripReference() const { return m_flags.stripped_reference; }
Expand All @@ -101,6 +111,10 @@ class FormattersMatchCandidate {

private:
ConstString m_type_name;
// If a formatter provides a matching callback function, we need the script
// interpreter and the type object (as an argument to the callback).
ScriptInterpreter *m_script_interpreter;
TypeImpl m_type;
Flags m_flags;
};

Expand Down
4 changes: 2 additions & 2 deletions lldb/include/lldb/DataFormatters/FormatManager.h
Expand Up @@ -128,12 +128,12 @@ class FormatManager : public IFormatChangeListener {
GetSyntheticChildren(ValueObject &valobj, lldb::DynamicValueType use_dynamic);

bool
AnyMatches(ConstString type_name,
AnyMatches(const FormattersMatchCandidate &candidate_type,
TypeCategoryImpl::FormatCategoryItems items =
TypeCategoryImpl::ALL_ITEM_TYPES,
bool only_enabled = true, const char **matching_category = nullptr,
TypeCategoryImpl::FormatCategoryItems *matching_type = nullptr) {
return m_categories_map.AnyMatches(type_name, items, only_enabled,
return m_categories_map.AnyMatches(candidate_type, items, only_enabled,
matching_category, matching_type);
}

Expand Down
52 changes: 38 additions & 14 deletions lldb/include/lldb/DataFormatters/FormattersContainer.h
Expand Up @@ -39,12 +39,16 @@ class IFormatChangeListener {

/// Class for matching type names.
class TypeMatcher {
/// Type name for exact match, or name of the python callback if m_match_type
/// is `eFormatterMatchCallback`.
ConstString m_name;
RegularExpression m_type_name_regex;
ConstString m_type_name;
/// Indicates what kind of matching strategy should be used:
/// - eFormatterMatchExact: match the exact type name in m_type_name.
/// - eFormatterMatchExact: match the exact type name in m_name.
/// - eFormatterMatchRegex: match using the RegularExpression object
/// `m_type_name_regex` instead.
/// - eFormatterMatchCallback: run the function in m_name to decide if a type
/// matches or not.
lldb::FormatterMatchType m_match_type;

// if the user tries to add formatters for, say, "struct Foo" those will not
Expand Down Expand Up @@ -73,35 +77,52 @@ class TypeMatcher {
TypeMatcher() = delete;
/// Creates a matcher that accepts any type with exactly the given type name.
TypeMatcher(ConstString type_name)
: m_type_name(type_name), m_match_type(lldb::eFormatterMatchExact) {}
: m_name(type_name), m_match_type(lldb::eFormatterMatchExact) {}
/// Creates a matcher that accepts any type matching the given regex.
TypeMatcher(RegularExpression regex)
: m_type_name_regex(std::move(regex)),
m_match_type(lldb::eFormatterMatchRegex) {}
/// Creates a matcher using the matching type and string from the given type
/// name specifier.
TypeMatcher(lldb::TypeNameSpecifierImplSP type_specifier)
: m_type_name(type_specifier->GetName()),
: m_name(type_specifier->GetName()),
m_match_type(type_specifier->GetMatchType()) {
if (m_match_type == lldb::eFormatterMatchRegex)
m_type_name_regex = RegularExpression(type_specifier->GetName());
}

/// True iff this matches the given type name.
bool Matches(ConstString type_name) const {
if (m_match_type == lldb::eFormatterMatchRegex)
/// True iff this matches the given type.
bool Matches(FormattersMatchCandidate candidate_type) const {
ConstString type_name = candidate_type.GetTypeName();
switch (m_match_type) {
case lldb::eFormatterMatchExact:
return m_name == type_name ||
StripTypeName(m_name) == StripTypeName(type_name);
case lldb::eFormatterMatchRegex:
return m_type_name_regex.Execute(type_name.GetStringRef());
return m_type_name == type_name ||
StripTypeName(m_type_name) == StripTypeName(type_name);
case lldb::eFormatterMatchCallback:
// CommandObjectType{Synth,Filter}Add tries to prevent the user from
// creating both a synthetic child provider and a filter for the same type
// in the same category, but we don't have a type object at that point, so
// it creates a dummy candidate without type or script interpreter.
// Skip callback matching in these cases.
if (candidate_type.GetScriptInterpreter())
return candidate_type.GetScriptInterpreter()->FormatterCallbackFunction(
m_name.AsCString(),
std::make_shared<TypeImpl>(candidate_type.GetType()));
}
return false;
}

lldb::FormatterMatchType GetMatchType() const { return m_match_type; }

/// Returns the underlying match string for this TypeMatcher.
ConstString GetMatchString() const {
if (m_match_type == lldb::eFormatterMatchExact)
return StripTypeName(m_name);
if (m_match_type == lldb::eFormatterMatchRegex)
return ConstString(m_type_name_regex.GetText());
return StripTypeName(m_type_name);
return ConstString(m_type_name_regex.GetText());
return m_name;
}

/// Returns true if this TypeMatcher and the given one were most created by
Expand Down Expand Up @@ -155,20 +176,23 @@ template <typename ValueType> class FormattersContainer {
return false;
}

bool Get(ConstString type, ValueSP &entry) {
// Finds the first formatter in the container that matches `candidate`.
bool Get(FormattersMatchCandidate candidate, ValueSP &entry) {
std::lock_guard<std::recursive_mutex> guard(m_map_mutex);
for (auto &formatter : llvm::reverse(m_map)) {
if (formatter.first.Matches(type)) {
if (formatter.first.Matches(candidate)) {
entry = formatter.second;
return true;
}
}
return false;
}

// Finds the first match between candidate types in `candidates` and
// formatters in this container.
bool Get(const FormattersMatchVector &candidates, ValueSP &entry) {
for (const FormattersMatchCandidate &candidate : candidates) {
if (Get(candidate.GetTypeName(), entry)) {
if (Get(candidate, entry)) {
if (candidate.IsMatch(entry) == false) {
entry.reset();
continue;
Expand Down
6 changes: 3 additions & 3 deletions lldb/include/lldb/DataFormatters/TypeCategory.h
Expand Up @@ -105,10 +105,10 @@ template <typename FormatterImpl> class TieredFormatterContainer {
return false;
}

bool AnyMatches(ConstString type_name) {
bool AnyMatches(const FormattersMatchCandidate &candidate) {
std::shared_ptr<FormatterImpl> entry;
for (auto sc : m_subcontainers) {
if (sc->Get(type_name, entry))
if (sc->Get(FormattersMatchVector{candidate}, entry))
return true;
}
return false;
Expand Down Expand Up @@ -346,7 +346,7 @@ class TypeCategoryImpl {

std::string GetDescription();

bool AnyMatches(ConstString type_name,
bool AnyMatches(const FormattersMatchCandidate &candidate_type,
FormatCategoryItems items = ALL_ITEM_TYPES,
bool only_enabled = true,
const char **matching_category = nullptr,
Expand Down
3 changes: 2 additions & 1 deletion lldb/include/lldb/DataFormatters/TypeCategoryMap.h
Expand Up @@ -17,6 +17,7 @@
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-public.h"

#include "lldb/DataFormatters/FormatClasses.h"
#include "lldb/DataFormatters/FormattersContainer.h"
#include "lldb/DataFormatters/TypeCategory.h"

Expand Down Expand Up @@ -69,7 +70,7 @@ class TypeCategoryMap {
lldb::TypeCategoryImplSP GetAtIndex(uint32_t);

bool
AnyMatches(ConstString type_name,
AnyMatches(const FormattersMatchCandidate &candidate_type,
TypeCategoryImpl::FormatCategoryItems items =
TypeCategoryImpl::ALL_ITEM_TYPES,
bool only_enabled = true, const char **matching_category = nullptr,
Expand Down
8 changes: 8 additions & 0 deletions lldb/include/lldb/Interpreter/ScriptInterpreter.h
Expand Up @@ -418,6 +418,14 @@ class ScriptInterpreter : public PluginInterface {
return false;
}

// Calls the specified formatter matching Python function and returns its
// result (true if it's a match, false if we should keep looking for a
// matching formatter).
virtual bool FormatterCallbackFunction(const char *function_name,
lldb::TypeImplSP type_impl_sp) {
return true;
}

virtual void Clear() {
// Clean up any ref counts to SBObjects that might be in global variables
}
Expand Down
2 changes: 1 addition & 1 deletion lldb/include/lldb/Target/Language.h
Expand Up @@ -175,7 +175,7 @@ class Language : public PluginInterface {
virtual HardcodedFormatters::HardcodedSyntheticFinder
GetHardcodedSynthetics();

virtual std::vector<ConstString>
virtual std::vector<FormattersMatchCandidate>
GetPossibleFormattersMatches(ValueObject &valobj,
lldb::DynamicValueType use_dynamic);

Expand Down
3 changes: 2 additions & 1 deletion lldb/include/lldb/lldb-enumerations.h
Expand Up @@ -835,8 +835,9 @@ enum TemplateArgumentKind {
enum FormatterMatchType {
eFormatterMatchExact,
eFormatterMatchRegex,
eFormatterMatchCallback,

eLastFormatterMatchType = eFormatterMatchRegex,
eLastFormatterMatchType = eFormatterMatchCallback,
};

/// Options that can be set for a formatter to alter its behavior. Not
Expand Down
8 changes: 6 additions & 2 deletions lldb/source/API/SBTypeNameSpecifier.cpp
Expand Up @@ -99,10 +99,14 @@ bool SBTypeNameSpecifier::GetDescription(
lldb::SBStream &description, lldb::DescriptionLevel description_level) {
LLDB_INSTRUMENT_VA(this, description, description_level);

lldb::FormatterMatchType match_type = GetMatchType();
const char *match_type_str =
(match_type == eFormatterMatchExact ? "plain"
: match_type == eFormatterMatchRegex ? "regex"
: "callback");
if (!IsValid())
return false;
description.Printf("SBTypeNameSpecifier(%s,%s)", GetName(),
IsRegex() ? "regex" : "plain");
description.Printf("SBTypeNameSpecifier(%s,%s)", GetName(), match_type_str);
return true;
}

Expand Down
18 changes: 16 additions & 2 deletions lldb/source/Commands/CommandObjectType.cpp
Expand Up @@ -11,6 +11,7 @@
#include "lldb/Core/Debugger.h"
#include "lldb/Core/IOHandler.h"
#include "lldb/DataFormatters/DataVisualization.h"
#include "lldb/DataFormatters/FormatClasses.h"
#include "lldb/Host/Config.h"
#include "lldb/Host/OptionParser.h"
#include "lldb/Interpreter/CommandInterpreter.h"
Expand Down Expand Up @@ -2302,7 +2303,13 @@ bool CommandObjectTypeSynthAdd::AddSynth(ConstString type_name,
// an actual type name. Matching a regex string against registered regexes
// doesn't work.
if (type == eRegularSynth) {
if (category->AnyMatches(type_name, eFormatCategoryItemFilter, false)) {
// 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.
FormattersMatchCandidate candidate_type(type_name, nullptr, TypeImpl(),
FormattersMatchCandidate::Flags());
if (category->AnyMatches(candidate_type, eFormatCategoryItemFilter,
false)) {
if (error)
error->SetErrorStringWithFormat("cannot add synthetic for type %s when "
"filter is defined in same category!",
Expand Down Expand Up @@ -2427,7 +2434,14 @@ class CommandObjectTypeFilterAdd : public CommandObjectParsed {
// if `type_name` is an actual type name. Matching a regex string against
// registered regexes doesn't work.
if (type == eRegularFilter) {
if (category->AnyMatches(type_name, eFormatCategoryItemSynth, false)) {
// 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.
FormattersMatchCandidate candidate_type(
type_name, nullptr, TypeImpl(), FormattersMatchCandidate::Flags());
lldb::SyntheticChildrenSP entry;
if (category->AnyMatches(candidate_type, eFormatCategoryItemSynth,
false)) {
if (error)
error->SetErrorStringWithFormat("cannot add filter for type %s when "
"synthetic is defined in same "
Expand Down
7 changes: 4 additions & 3 deletions lldb/source/DataFormatters/DataVisualization.cpp
Expand Up @@ -66,10 +66,11 @@ DataVisualization::GetSyntheticForType(lldb::TypeNameSpecifierImplSP type_sp) {
}

bool DataVisualization::AnyMatches(
ConstString type_name, TypeCategoryImpl::FormatCategoryItems items,
bool only_enabled, const char **matching_category,
const FormattersMatchCandidate &candidate_type,
TypeCategoryImpl::FormatCategoryItems items, bool only_enabled,
const char **matching_category,
TypeCategoryImpl::FormatCategoryItems *matching_type) {
return GetFormatManager().AnyMatches(type_name, items, only_enabled,
return GetFormatManager().AnyMatches(candidate_type, items, only_enabled,
matching_category, matching_type);
}

Expand Down

0 comments on commit d765664

Please sign in to comment.