From d76566417e592cfac9c710f82575473b1b4a9285 Mon Sep 17 00:00:00 2001 From: Jorge Gorbe Moya Date: Tue, 11 Oct 2022 00:44:06 -0700 Subject: [PATCH] [lldb] Add matching based on Python callbacks for data formatters. 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 --- lldb/bindings/python/python-swigsafecast.swig | 4 + lldb/bindings/python/python-wrapper.swig | 26 +++++ lldb/include/lldb/API/SBType.h | 2 +- .../lldb/DataFormatters/DataVisualization.h | 2 +- .../lldb/DataFormatters/FormatClasses.h | 18 +++- .../lldb/DataFormatters/FormatManager.h | 4 +- .../lldb/DataFormatters/FormattersContainer.h | 52 +++++++--- .../lldb/DataFormatters/TypeCategory.h | 6 +- .../lldb/DataFormatters/TypeCategoryMap.h | 3 +- .../lldb/Interpreter/ScriptInterpreter.h | 8 ++ lldb/include/lldb/Target/Language.h | 2 +- lldb/include/lldb/lldb-enumerations.h | 3 +- lldb/source/API/SBTypeNameSpecifier.cpp | 8 +- lldb/source/Commands/CommandObjectType.cpp | 18 +++- .../DataFormatters/DataVisualization.cpp | 7 +- lldb/source/DataFormatters/FormatManager.cpp | 16 +++- lldb/source/DataFormatters/TypeCategory.cpp | 21 ++-- .../source/DataFormatters/TypeCategoryMap.cpp | 7 +- .../Plugins/Language/ObjC/ObjCLanguage.cpp | 10 +- .../Plugins/Language/ObjC/ObjCLanguage.h | 2 +- .../Python/SWIGPythonBridge.h | 4 + .../Python/ScriptInterpreterPython.cpp | 8 ++ .../Python/ScriptInterpreterPythonImpl.h | 3 + lldb/source/Target/Language.cpp | 2 +- .../data-formatter/callback-matching/Makefile | 3 + .../TestDataFormatterCallbackMatching.py | 49 ++++++++++ .../formatters_with_callback.py | 39 ++++++++ .../data-formatter/callback-matching/main.cpp | 16 ++++ .../DataFormatter/FormattersContainerTest.cpp | 96 ++++++++++--------- .../Python/PythonTestSuite.cpp | 6 ++ 30 files changed, 342 insertions(+), 103 deletions(-) create mode 100644 lldb/test/API/functionalities/data-formatter/callback-matching/Makefile create mode 100644 lldb/test/API/functionalities/data-formatter/callback-matching/TestDataFormatterCallbackMatching.py create mode 100644 lldb/test/API/functionalities/data-formatter/callback-matching/formatters_with_callback.py create mode 100644 lldb/test/API/functionalities/data-formatter/callback-matching/main.cpp diff --git a/lldb/bindings/python/python-swigsafecast.swig b/lldb/bindings/python/python-swigsafecast.swig index eb684133abef7..aa5e8e50a2d92 100644 --- a/lldb/bindings/python/python-swigsafecast.swig +++ b/lldb/bindings/python/python-swigsafecast.swig @@ -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); diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index 626fc47bebb9f..adac8a405ab9c 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -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( + session_dictionary_name); + auto pfunc = PythonObject::ResolveNameWithDictionary( + 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, diff --git a/lldb/include/lldb/API/SBType.h b/lldb/include/lldb/API/SBType.h index aa45aeeec476d..215e03fad99ba 100644 --- a/lldb/include/lldb/API/SBType.h +++ b/lldb/include/lldb/API/SBType.h @@ -106,6 +106,7 @@ class SBType { SBType(); SBType(const lldb::SBType &rhs); + SBType(const lldb::TypeImplSP &); ~SBType(); @@ -239,7 +240,6 @@ class SBType { SBType(const lldb_private::CompilerType &); SBType(const lldb::TypeSP &); - SBType(const lldb::TypeImplSP &); }; class SBTypeList { diff --git a/lldb/include/lldb/DataFormatters/DataVisualization.h b/lldb/include/lldb/DataFormatters/DataVisualization.h index 7be07d65acdde..5aea29132b8fc 100644 --- a/lldb/include/lldb/DataFormatters/DataVisualization.h +++ b/lldb/include/lldb/DataFormatters/DataVisualization.h @@ -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, diff --git a/lldb/include/lldb/DataFormatters/FormatClasses.h b/lldb/include/lldb/DataFormatters/FormatClasses.h index ac2b070a55cdc..a6bc3a3542536 100644 --- a/lldb/include/lldb/DataFormatters/FormatClasses.h +++ b/lldb/include/lldb/DataFormatters/FormatClasses.h @@ -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" @@ -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; } @@ -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; }; diff --git a/lldb/include/lldb/DataFormatters/FormatManager.h b/lldb/include/lldb/DataFormatters/FormatManager.h index 594addd1f083f..295d3b84342a5 100644 --- a/lldb/include/lldb/DataFormatters/FormatManager.h +++ b/lldb/include/lldb/DataFormatters/FormatManager.h @@ -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); } diff --git a/lldb/include/lldb/DataFormatters/FormattersContainer.h b/lldb/include/lldb/DataFormatters/FormattersContainer.h index 8a93c0345cbe5..fd046e773b690 100644 --- a/lldb/include/lldb/DataFormatters/FormattersContainer.h +++ b/lldb/include/lldb/DataFormatters/FormattersContainer.h @@ -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 @@ -73,7 +77,7 @@ 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)), @@ -81,27 +85,44 @@ class TypeMatcher { /// 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(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 @@ -155,10 +176,11 @@ template 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 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; } @@ -166,9 +188,11 @@ template class FormattersContainer { 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; diff --git a/lldb/include/lldb/DataFormatters/TypeCategory.h b/lldb/include/lldb/DataFormatters/TypeCategory.h index bad39aa676af3..884a27d76e055 100644 --- a/lldb/include/lldb/DataFormatters/TypeCategory.h +++ b/lldb/include/lldb/DataFormatters/TypeCategory.h @@ -105,10 +105,10 @@ template class TieredFormatterContainer { return false; } - bool AnyMatches(ConstString type_name) { + bool AnyMatches(const FormattersMatchCandidate &candidate) { std::shared_ptr entry; for (auto sc : m_subcontainers) { - if (sc->Get(type_name, entry)) + if (sc->Get(FormattersMatchVector{candidate}, entry)) return true; } return false; @@ -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, diff --git a/lldb/include/lldb/DataFormatters/TypeCategoryMap.h b/lldb/include/lldb/DataFormatters/TypeCategoryMap.h index 45dbb306aa758..d4f7634c90b06 100644 --- a/lldb/include/lldb/DataFormatters/TypeCategoryMap.h +++ b/lldb/include/lldb/DataFormatters/TypeCategoryMap.h @@ -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" @@ -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, diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h index cb3cafaf2ed51..f34ce43e946eb 100644 --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -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 } diff --git a/lldb/include/lldb/Target/Language.h b/lldb/include/lldb/Target/Language.h index fa79aaee05748..89136cc5e0ff5 100644 --- a/lldb/include/lldb/Target/Language.h +++ b/lldb/include/lldb/Target/Language.h @@ -175,7 +175,7 @@ class Language : public PluginInterface { virtual HardcodedFormatters::HardcodedSyntheticFinder GetHardcodedSynthetics(); - virtual std::vector + virtual std::vector GetPossibleFormattersMatches(ValueObject &valobj, lldb::DynamicValueType use_dynamic); diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 2ac1a74214b42..3ba29a3013821 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -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 diff --git a/lldb/source/API/SBTypeNameSpecifier.cpp b/lldb/source/API/SBTypeNameSpecifier.cpp index d1dc2953c9b93..8a6eb086a9b15 100644 --- a/lldb/source/API/SBTypeNameSpecifier.cpp +++ b/lldb/source/API/SBTypeNameSpecifier.cpp @@ -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; } diff --git a/lldb/source/Commands/CommandObjectType.cpp b/lldb/source/Commands/CommandObjectType.cpp index 63d3c6979ec31..ccbe7922e65fe 100644 --- a/lldb/source/Commands/CommandObjectType.cpp +++ b/lldb/source/Commands/CommandObjectType.cpp @@ -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" @@ -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!", @@ -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 " diff --git a/lldb/source/DataFormatters/DataVisualization.cpp b/lldb/source/DataFormatters/DataVisualization.cpp index 53832492aa253..036c9372baf81 100644 --- a/lldb/source/DataFormatters/DataVisualization.cpp +++ b/lldb/source/DataFormatters/DataVisualization.cpp @@ -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); } diff --git a/lldb/source/DataFormatters/FormatManager.cpp b/lldb/source/DataFormatters/FormatManager.cpp index db9f6057e8429..166264df99337 100644 --- a/lldb/source/DataFormatters/FormatManager.cpp +++ b/lldb/source/DataFormatters/FormatManager.cpp @@ -11,6 +11,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/DataFormatters/FormattersHelpers.h" #include "lldb/DataFormatters/LanguageCategory.h" +#include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Language.h" #include "lldb/Utility/LLDBLog.h" @@ -178,19 +179,24 @@ void FormatManager::GetPossibleMatches( FormattersMatchCandidate::Flags current_flags, bool root_level) { compiler_type = compiler_type.GetTypeForFormatters(); ConstString type_name(compiler_type.GetTypeName()); + ScriptInterpreter *script_interpreter = + valobj.GetTargetSP()->GetDebugger().GetScriptInterpreter(); if (valobj.GetBitfieldBitSize() > 0) { StreamString sstring; sstring.Printf("%s:%d", type_name.AsCString(), valobj.GetBitfieldBitSize()); ConstString bitfieldname(sstring.GetString()); - entries.push_back({bitfieldname, current_flags}); + entries.push_back({bitfieldname, script_interpreter, + TypeImpl(compiler_type), current_flags}); } if (!compiler_type.IsMeaninglessWithoutDynamicResolution()) { - entries.push_back({type_name, current_flags}); + entries.push_back({type_name, script_interpreter, TypeImpl(compiler_type), + current_flags}); ConstString display_type_name(compiler_type.GetTypeName()); if (display_type_name != type_name) - entries.push_back({display_type_name, current_flags}); + entries.push_back({display_type_name, script_interpreter, + TypeImpl(compiler_type), current_flags}); } for (bool is_rvalue_ref = true, j = true; @@ -245,9 +251,9 @@ void FormatManager::GetPossibleMatches( for (lldb::LanguageType language_type : GetCandidateLanguages(valobj.GetObjectRuntimeLanguage())) { if (Language *language = Language::FindPlugin(language_type)) { - for (ConstString candidate : + for (const FormattersMatchCandidate& candidate : language->GetPossibleFormattersMatches(valobj, use_dynamic)) { - entries.push_back({candidate, current_flags}); + entries.push_back(candidate); } } } diff --git a/lldb/source/DataFormatters/TypeCategory.cpp b/lldb/source/DataFormatters/TypeCategory.cpp index 1d56749682455..4d8663ea9c03e 100644 --- a/lldb/source/DataFormatters/TypeCategory.cpp +++ b/lldb/source/DataFormatters/TypeCategory.cpp @@ -184,20 +184,15 @@ uint32_t TypeCategoryImpl::GetCount(FormatCategoryItems items) { return count; } -bool TypeCategoryImpl::AnyMatches(ConstString type_name, - FormatCategoryItems items, bool only_enabled, - const char **matching_category, - FormatCategoryItems *matching_type) { +bool TypeCategoryImpl::AnyMatches( + const FormattersMatchCandidate &candidate_type, FormatCategoryItems items, + bool only_enabled, const char **matching_category, + FormatCategoryItems *matching_type) { if (!IsEnabled() && only_enabled) return false; - lldb::TypeFormatImplSP format_sp; - lldb::TypeSummaryImplSP summary_sp; - TypeFilterImpl::SharedPointer filter_sp; - ScriptedSyntheticChildren::SharedPointer synth_sp; - if (items & eFormatCategoryItemFormat) { - if (m_format_cont.AnyMatches(type_name)) { + if (m_format_cont.AnyMatches(candidate_type)) { if (matching_category) *matching_category = m_name.GetCString(); if (matching_type) @@ -207,7 +202,7 @@ bool TypeCategoryImpl::AnyMatches(ConstString type_name, } if (items & eFormatCategoryItemSummary) { - if (m_summary_cont.AnyMatches(type_name)) { + if (m_summary_cont.AnyMatches(candidate_type)) { if (matching_category) *matching_category = m_name.GetCString(); if (matching_type) @@ -217,7 +212,7 @@ bool TypeCategoryImpl::AnyMatches(ConstString type_name, } if (items & eFormatCategoryItemFilter) { - if (m_filter_cont.AnyMatches(type_name)) { + if (m_filter_cont.AnyMatches(candidate_type)) { if (matching_category) *matching_category = m_name.GetCString(); if (matching_type) @@ -227,7 +222,7 @@ bool TypeCategoryImpl::AnyMatches(ConstString type_name, } if (items & eFormatCategoryItemSynth) { - if (m_synth_cont.AnyMatches(type_name)) { + if (m_synth_cont.AnyMatches(candidate_type)) { if (matching_category) *matching_category = m_name.GetCString(); if (matching_type) diff --git a/lldb/source/DataFormatters/TypeCategoryMap.cpp b/lldb/source/DataFormatters/TypeCategoryMap.cpp index aa8387b4deec9..55635173cc8c5 100644 --- a/lldb/source/DataFormatters/TypeCategoryMap.cpp +++ b/lldb/source/DataFormatters/TypeCategoryMap.cpp @@ -154,14 +154,15 @@ bool TypeCategoryMap::Get(uint32_t pos, ValueSP &entry) { } bool TypeCategoryMap::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) { std::lock_guard guard(m_map_mutex); MapIterator pos, end = m_map.end(); for (pos = m_map.begin(); pos != end; pos++) { - if (pos->second->AnyMatches(type_name, items, only_enabled, + if (pos->second->AnyMatches(candidate_type, items, only_enabled, matching_category, matching_type)) return true; } diff --git a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp index 11d5b0813b586..9cbb404191673 100644 --- a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp +++ b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp @@ -12,6 +12,7 @@ #include "Plugins/ExpressionParser/Clang/ClangUtil.h" #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" +#include "lldb/Core/Debugger.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/ValueObject.h" #include "lldb/DataFormatters/DataVisualization.h" @@ -931,10 +932,10 @@ lldb::TypeCategoryImplSP ObjCLanguage::GetFormatters() { return g_category; } -std::vector +std::vector ObjCLanguage::GetPossibleFormattersMatches(ValueObject &valobj, lldb::DynamicValueType use_dynamic) { - std::vector result; + std::vector result; if (use_dynamic == lldb::eNoDynamicValues) return result; @@ -959,7 +960,10 @@ ObjCLanguage::GetPossibleFormattersMatches(ValueObject &valobj, if (!objc_class_sp) break; if (ConstString name = objc_class_sp->GetClassName()) - result.push_back(name); + result.push_back( + {name, valobj.GetTargetSP()->GetDebugger().GetScriptInterpreter(), + TypeImpl(objc_class_sp->GetType()), + FormattersMatchCandidate::Flags{}}); } while (false); } diff --git a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h index 914452086db7a..b61348a3280ed 100644 --- a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h +++ b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h @@ -108,7 +108,7 @@ class ObjCLanguage : public Language { lldb::TypeCategoryImplSP GetFormatters() override; - std::vector + std::vector GetPossibleFormattersMatches(ValueObject &valobj, lldb::DynamicValueType use_dynamic) override; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h index 4df2353567374..7e18b0ef08046 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -75,6 +75,10 @@ bool LLDBSwigPythonWatchpointCallbackFunction( const char *python_function_name, const char *session_dictionary_name, const lldb::StackFrameSP &sb_frame, const lldb::WatchpointSP &sb_wp); +bool LLDBSwigPythonFormatterCallbackFunction( + const char *python_function_name, const char *session_dictionary_name, + lldb::TypeImplSP type_impl_sp); + bool LLDBSwigPythonCallTypeScript(const char *python_function_name, const void *session_dictionary, const lldb::ValueObjectSP &valobj_sp, diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp index d0f67a5684c5e..37e3c94df870a 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -2154,6 +2154,14 @@ bool ScriptInterpreterPythonImpl::GetScriptedSummary( return ret_val; } +bool ScriptInterpreterPythonImpl::FormatterCallbackFunction( + const char *python_function_name, TypeImplSP type_impl_sp) { + Locker py_lock(this, + Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN); + return LLDBSwigPythonFormatterCallbackFunction( + python_function_name, m_dictionary_name.c_str(), type_impl_sp); +} + bool ScriptInterpreterPythonImpl::BreakpointCallbackFunction( void *baton, StoppointCallbackContext *context, user_id_t break_id, user_id_t break_loc_id) { diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h index 3b80c67d201ad..f4875bfb8d18a 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -202,6 +202,9 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython { const TypeSummaryOptions &options, std::string &retval) override; + bool FormatterCallbackFunction(const char *function_name, + lldb::TypeImplSP type_impl_sp) override; + bool GetDocumentationForItem(const char *item, std::string &dest) override; bool GetShortHelpForCommandObject(StructuredData::GenericSP cmd_obj_sp, diff --git a/lldb/source/Target/Language.cpp b/lldb/source/Target/Language.cpp index 6df36aeeb7b72..92431005cba91 100644 --- a/lldb/source/Target/Language.cpp +++ b/lldb/source/Target/Language.cpp @@ -144,7 +144,7 @@ Language::GetHardcodedSynthetics() { return {}; } -std::vector +std::vector Language::GetPossibleFormattersMatches(ValueObject &valobj, lldb::DynamicValueType use_dynamic) { return {}; diff --git a/lldb/test/API/functionalities/data-formatter/callback-matching/Makefile b/lldb/test/API/functionalities/data-formatter/callback-matching/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/callback-matching/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/functionalities/data-formatter/callback-matching/TestDataFormatterCallbackMatching.py b/lldb/test/API/functionalities/data-formatter/callback-matching/TestDataFormatterCallbackMatching.py new file mode 100644 index 0000000000000..91403d6d5f163 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/callback-matching/TestDataFormatterCallbackMatching.py @@ -0,0 +1,49 @@ +""" +Test lldb data formatter callback-based matching. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class PythonSynthDataFormatterTestCase(TestBase): + + def setUp(self): + # Call super's setUp(). + TestBase.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): + """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") + + # 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 new file mode 100644 index 0000000000000..60e919a94352e --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/callback-matching/formatters_with_callback.py @@ -0,0 +1,39 @@ +import lldb + +def derives_from_base(sbtype, internal_dict): + for base in sbtype.get_bases_array(): + if base.GetName() == "Base": + return True + return False + + +class SynthProvider: + def __init__(self, valobj, dict): + self.valobj = valobj + + def num_children(self): + return 1 + + def get_child_index(self, name): + return 0 + + def get_child_at_index(self, index): + if index == 0: + return self.valobj.CreateValueFromExpression("synthetic_child", + "9999") + return None + + +def __lldb_init_module(debugger, dict): + cat = debugger.CreateCategory("callback_formatters") + cat.AddTypeSummary( + lldb.SBTypeNameSpecifier("formatters_with_callback.derives_from_base", + lldb.eFormatterMatchCallback), + lldb.SBTypeSummary.CreateWithScriptCode( + "return 'hello from callback summary'")) + cat.AddTypeSynthetic( + lldb.SBTypeNameSpecifier('formatters_with_callback.derives_from_base', + lldb.eFormatterMatchCallback), + lldb.SBTypeSynthetic.CreateWithClassName( + 'formatters_with_callback.SynthProvider')) + cat.SetEnabled(True) diff --git a/lldb/test/API/functionalities/data-formatter/callback-matching/main.cpp b/lldb/test/API/functionalities/data-formatter/callback-matching/main.cpp new file mode 100644 index 0000000000000..7732d87342a93 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/callback-matching/main.cpp @@ -0,0 +1,16 @@ +struct Base { int x; }; +struct Derived : public Base { int y; }; + +struct NonDerived { int z; }; + +int main() +{ + Base base = {1111}; + + Derived derived; + derived.x = 2222; + derived.y = 3333; + + NonDerived nd = {4444}; + return 0; // Set break point at this line. +} diff --git a/lldb/unittests/DataFormatter/FormattersContainerTest.cpp b/lldb/unittests/DataFormatter/FormattersContainerTest.cpp index a28212391eae9..41b01adfb9ecd 100644 --- a/lldb/unittests/DataFormatter/FormattersContainerTest.cpp +++ b/lldb/unittests/DataFormatter/FormattersContainerTest.cpp @@ -7,12 +7,20 @@ //===----------------------------------------------------------------------===// #include "lldb/DataFormatters/FormattersContainer.h" +#include "lldb/DataFormatters/FormatClasses.h" #include "gtest/gtest.h" using namespace lldb; using namespace lldb_private; +// Creates a dummy candidate with just a type name in order to test the string +// matching (exact name match and regex match) paths. +FormattersMatchCandidate CandidateFromTypeName(const char *type_name) { + return FormattersMatchCandidate(ConstString(type_name), nullptr, TypeImpl(), + FormattersMatchCandidate::Flags()); +} + // All the prefixes that the exact name matching will strip from the type. static const std::vector exact_name_prefixes = { "", // no prefix. @@ -25,63 +33,63 @@ TEST(TypeMatcherTests, ExactName) { SCOPED_TRACE("Prefix: " + prefix); TypeMatcher matcher(ConstString(prefix + "Name")); - EXPECT_TRUE(matcher.Matches(ConstString("class Name"))); - EXPECT_TRUE(matcher.Matches(ConstString("struct Name"))); - EXPECT_TRUE(matcher.Matches(ConstString("union Name"))); - EXPECT_TRUE(matcher.Matches(ConstString("enum Name"))); - EXPECT_TRUE(matcher.Matches(ConstString("Name"))); - - EXPECT_FALSE(matcher.Matches(ConstString("Name "))); - EXPECT_FALSE(matcher.Matches(ConstString("ame"))); - EXPECT_FALSE(matcher.Matches(ConstString("Nam"))); - EXPECT_FALSE(matcher.Matches(ConstString("am"))); - EXPECT_FALSE(matcher.Matches(ConstString("a"))); - EXPECT_FALSE(matcher.Matches(ConstString(" "))); - EXPECT_FALSE(matcher.Matches(ConstString("class N"))); - EXPECT_FALSE(matcher.Matches(ConstString("class "))); - EXPECT_FALSE(matcher.Matches(ConstString("class"))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("class Name"))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("struct Name"))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("union Name"))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("enum Name"))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("Name"))); + + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("Name "))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("ame"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("Nam"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("am"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("a"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName(" "))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("class N"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("class "))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("class"))); } } // TypeMatcher that uses a regex to match a type name. TEST(TypeMatcherTests, RegexName) { TypeMatcher matcher(RegularExpression("^a[a-z]c$")); - EXPECT_TRUE(matcher.Matches(ConstString("abc"))); - EXPECT_TRUE(matcher.Matches(ConstString("azc"))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("abc"))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("azc"))); // FIXME: This isn't consistent with the 'exact' type name matches above. - EXPECT_FALSE(matcher.Matches(ConstString("class abc"))); - - EXPECT_FALSE(matcher.Matches(ConstString("abbc"))); - EXPECT_FALSE(matcher.Matches(ConstString(" abc"))); - EXPECT_FALSE(matcher.Matches(ConstString("abc "))); - EXPECT_FALSE(matcher.Matches(ConstString(" abc "))); - EXPECT_FALSE(matcher.Matches(ConstString("XabcX"))); - EXPECT_FALSE(matcher.Matches(ConstString("ac"))); - EXPECT_FALSE(matcher.Matches(ConstString("a[a-z]c"))); - EXPECT_FALSE(matcher.Matches(ConstString("aAc"))); - EXPECT_FALSE(matcher.Matches(ConstString("ABC"))); - EXPECT_FALSE(matcher.Matches(ConstString(""))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("class abc"))); + + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("abbc"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName(" abc"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("abc "))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName(" abc "))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("XabcX"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("ac"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("a[a-z]c"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("aAc"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("ABC"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName(""))); } // TypeMatcher that only searches the type name. TEST(TypeMatcherTests, RegexMatchPart) { TypeMatcher matcher(RegularExpression("a[a-z]c")); - EXPECT_TRUE(matcher.Matches(ConstString("class abc"))); - EXPECT_TRUE(matcher.Matches(ConstString("abc"))); - EXPECT_TRUE(matcher.Matches(ConstString(" abc "))); - EXPECT_TRUE(matcher.Matches(ConstString("azc"))); - EXPECT_TRUE(matcher.Matches(ConstString("abc "))); - EXPECT_TRUE(matcher.Matches(ConstString(" abc "))); - EXPECT_TRUE(matcher.Matches(ConstString(" abc"))); - EXPECT_TRUE(matcher.Matches(ConstString("XabcX"))); - - EXPECT_FALSE(matcher.Matches(ConstString("abbc"))); - EXPECT_FALSE(matcher.Matches(ConstString("ac"))); - EXPECT_FALSE(matcher.Matches(ConstString("a[a-z]c"))); - EXPECT_FALSE(matcher.Matches(ConstString("aAc"))); - EXPECT_FALSE(matcher.Matches(ConstString("ABC"))); - EXPECT_FALSE(matcher.Matches(ConstString(""))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("class abc"))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("abc"))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName(" abc "))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("azc"))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("abc "))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName(" abc "))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName(" abc"))); + EXPECT_TRUE(matcher.Matches(CandidateFromTypeName("XabcX"))); + + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("abbc"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("ac"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("a[a-z]c"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("aAc"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName("ABC"))); + EXPECT_FALSE(matcher.Matches(CandidateFromTypeName(""))); } // GetMatchString for exact type name matchers. diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp index 6ac4606b5a845..87e4a03ee77ba 100644 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -67,6 +67,12 @@ bool lldb_private::LLDBSwigPythonWatchpointCallbackFunction( return false; } +bool lldb_private::LLDBSwigPythonFormatterCallbackFunction( + const char *python_function_name, const char *session_dictionary_name, + lldb::TypeImplSP type_impl_sp) { + return false; +} + bool lldb_private::LLDBSwigPythonCallTypeScript( const char *python_function_name, const void *session_dictionary, const lldb::ValueObjectSP &valobj_sp, void **pyfunct_wrapper,