diff --git a/lldb/bindings/python/python-swigsafecast.swig b/lldb/bindings/python/python-swigsafecast.swig --- a/lldb/bindings/python/python-swigsafecast.swig +++ b/lldb/bindings/python/python-swigsafecast.swig @@ -97,6 +97,10 @@ 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 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -90,6 +90,32 @@ 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 --- a/lldb/include/lldb/API/SBType.h +++ b/lldb/include/lldb/API/SBType.h @@ -106,6 +106,7 @@ SBType(); SBType(const lldb::SBType &rhs); + SBType(const lldb::TypeImplSP &); ~SBType(); @@ -239,7 +240,6 @@ 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 --- a/lldb/include/lldb/DataFormatters/DataVisualization.h +++ b/lldb/include/lldb/DataFormatters/DataVisualization.h @@ -51,7 +51,7 @@ 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 --- 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 @@ } }; - 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 @@ 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 --- a/lldb/include/lldb/DataFormatters/FormatManager.h +++ b/lldb/include/lldb/DataFormatters/FormatManager.h @@ -128,12 +128,12 @@ 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 --- a/lldb/include/lldb/DataFormatters/FormattersContainer.h +++ b/lldb/include/lldb/DataFormatters/FormattersContainer.h @@ -39,12 +39,16 @@ /// 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 @@ 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 @@ /// 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 @@ 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 @@ 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 --- a/lldb/include/lldb/DataFormatters/TypeCategory.h +++ b/lldb/include/lldb/DataFormatters/TypeCategory.h @@ -105,10 +105,10 @@ 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 @@ 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 --- 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 @@ 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 --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -418,6 +418,14 @@ 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 --- a/lldb/include/lldb/Target/Language.h +++ b/lldb/include/lldb/Target/Language.h @@ -175,7 +175,7 @@ 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 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -835,8 +835,9 @@ 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 --- a/lldb/source/API/SBTypeNameSpecifier.cpp +++ b/lldb/source/API/SBTypeNameSpecifier.cpp @@ -99,10 +99,14 @@ 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 --- 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 @@ // 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 @@ // 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 --- a/lldb/source/DataFormatters/DataVisualization.cpp +++ b/lldb/source/DataFormatters/DataVisualization.cpp @@ -66,10 +66,11 @@ } 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 --- 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 @@ 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 @@ 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 --- a/lldb/source/DataFormatters/TypeCategory.cpp +++ b/lldb/source/DataFormatters/TypeCategory.cpp @@ -184,20 +184,15 @@ 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 @@ } 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 @@ } 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 @@ } 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 --- a/lldb/source/DataFormatters/TypeCategoryMap.cpp +++ b/lldb/source/DataFormatters/TypeCategoryMap.cpp @@ -154,14 +154,15 @@ } 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.h b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h --- a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h +++ b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h @@ -108,7 +108,7 @@ lldb::TypeCategoryImplSP GetFormatters() override; - std::vector + std::vector GetPossibleFormattersMatches(ValueObject &valobj, lldb::DynamicValueType use_dynamic) override; diff --git a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp --- 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 @@ 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 @@ 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/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -75,6 +75,10 @@ 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 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -2154,6 +2154,14 @@ 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 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -202,6 +202,9 @@ 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 --- a/lldb/source/Target/Language.cpp +++ b/lldb/source/Target/Language.cpp @@ -144,7 +144,7 @@ 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 --- /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 --- /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 --- /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 --- /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 --- 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 @@ 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 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -67,6 +67,12 @@ 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,