Author: Med Ismail Bennani Date: 2026-07-16T18:00:26-07:00 New Revision: a07b91d8243b8a8051dbbe5739eacc7942269c61
URL: https://github.com/llvm/llvm-project/commit/a07b91d8243b8a8051dbbe5739eacc7942269c61 DIFF: https://github.com/llvm/llvm-project/commit/a07b91d8243b8a8051dbbe5739eacc7942269c61.diff LOG: [lldb/script] Migrate frame recognizers onto ScriptedPythonInterface (#209805) Give `frame recognizer add -l` a formal ScriptedStackFrameRecognizerInterface, matching the architecture already used by ScriptedProcess, ScriptedBreakpoint, etc.: a C++ interface header, a Python-backed implementation built on ScriptedPythonInterface's CreatePluginObject/Dispatch machinery, PluginManager registration, and a generatable Python ABC template (scripted_stackframe_recognizer.py). get_recognized_arguments' documented contract returns a plain list of lldb.SBValue (not an lldb.SBValueList), so it bypasses the generic Dispatch<ValueObjectListSP>() extractor and calls the SWIG bridge directly, matching the legacy behavior exactly. ScriptedStackFrameRecognizer now holds a single interface object created once in its constructor and reused across every RecognizeFrame() call, same lifecycle as before. Signed-off-by: Med Ismail Bennani <[email protected]> Added: lldb/examples/python/templates/scripted_stackframe_recognizer.py lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h Modified: lldb/bindings/python/CMakeLists.txt lldb/bindings/python/python-wrapper.swig lldb/docs/CMakeLists.txt lldb/include/lldb/Interpreter/ScriptInterpreter.h lldb/include/lldb/Target/StackFrameRecognizer.h lldb/include/lldb/lldb-enumerations.h lldb/include/lldb/lldb-forward.h lldb/source/Interpreter/ScriptInterpreter.cpp lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h lldb/source/Target/StackFrameRecognizer.cpp lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py lldb/test/API/commands/frame/recognizer/recognizer.py lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp Removed: ################################################################################ diff --git a/lldb/bindings/python/CMakeLists.txt b/lldb/bindings/python/CMakeLists.txt index 9a8a8d420e18a..d29b143c1408c 100644 --- a/lldb/bindings/python/CMakeLists.txt +++ b/lldb/bindings/python/CMakeLists.txt @@ -119,6 +119,7 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_thread_plan.py" "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_breakpoint.py" "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_hook.py" + "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_stackframe_recognizer.py" ) if(APPLE) diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index dea4f6b4c7f7c..2392737402e20 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -798,53 +798,6 @@ PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPythonCreateOSPlugin( return pfunc(SWIGBridge::ToSWIGWrapper(process_sp)); } -PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPython_CreateFrameRecognizer( - const char *python_class_name, const char *session_dictionary_name) { - if (python_class_name == NULL || python_class_name[0] == '\0' || - !session_dictionary_name) - return PythonObject(); - - PyErr_Cleaner py_err_cleaner(true); - - auto dict = PythonModule::MainModule().ResolveName<PythonDictionary>( - session_dictionary_name); - auto pfunc = PythonObject::ResolveNameWithDictionary<PythonCallable>( - python_class_name, dict); - - if (!pfunc.IsAllocated()) - return PythonObject(); - - return pfunc(); -} - -PyObject *lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArguments( - PyObject *implementor, const lldb::StackFrameSP &frame_sp) { - static char callee_name[] = "get_recognized_arguments"; - - PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp); - - PythonString str(callee_name); - PyObject *result = - PyObject_CallMethodObjArgs(implementor, str.get(), arg.get(), NULL); - return result; -} - -bool lldb_private::python::SWIGBridge::LLDBSwigPython_ShouldHide( - PyObject *implementor, const lldb::StackFrameSP &frame_sp) { - static char callee_name[] = "should_hide"; - - PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp); - - PythonString str(callee_name); - - PyObject *result = - PyObject_CallMethodObjArgs(implementor, str.get(), arg.get(), NULL); - bool ret_val = result ? PyObject_IsTrue(result) : false; - Py_XDECREF(result); - - return ret_val; -} - void *lldb_private::python::SWIGBridge::LLDBSWIGPython_GetDynamicSetting( void *module, const char *setting, const lldb::TargetSP &target_sp) { if (!module || !setting) diff --git a/lldb/docs/CMakeLists.txt b/lldb/docs/CMakeLists.txt index 58c740f16bf78..dd091836dc1aa 100644 --- a/lldb/docs/CMakeLists.txt +++ b/lldb/docs/CMakeLists.txt @@ -32,6 +32,7 @@ if (LLDB_ENABLE_PYTHON AND SPHINX_FOUND) COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_thread_plan.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/" COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_breakpoint.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/" COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_hook.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/" + COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_stackframe_recognizer.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/" COMMENT "Copying lldb.py to pretend its a Python package.") add_dependencies(lldb-python-doc-package swig_wrapper_python) diff --git a/lldb/examples/python/templates/scripted_stackframe_recognizer.py b/lldb/examples/python/templates/scripted_stackframe_recognizer.py new file mode 100644 index 0000000000000..0c8489f8d6d2d --- /dev/null +++ b/lldb/examples/python/templates/scripted_stackframe_recognizer.py @@ -0,0 +1,102 @@ +from abc import ABCMeta +from typing import Optional + +import lldb + + +class ScriptedStackFrameRecognizer(metaclass=ABCMeta): + """ + The base class for a scripted stack frame recognizer. + + A frame recognizer inspects a stack frame at stop time and can: + + - attach recognized arguments to it (surfaced the same way as args + for unrecognized frames); + - hide the frame from backtraces; + - override the stop description reported for it; + - expose an exception object associated with it (e.g. a runtime's + thrown value); + - redirect auto-selection to a more relevant frame (e.g. bounce past + a language-runtime trampoline). + + Register the recognizer with `frame recognizer add -l <ClassName> ...`. + + Every method is optional: a recognizer only implements the ones it + cares about. A recognizer that just hides frames can implement only + `should_hide`; one that surfaces exception info can implement only + `get_exception` and `get_stop_description`. + """ + + def __init__(self): + """Construct a scripted stack frame recognizer. + + Recognizers are constructed with no arguments and are shared across + every frame they're asked to recognize. + """ + pass + + def get_recognized_arguments(self, frame: lldb.SBFrame) -> list: + """Get the arguments recognized for this frame. + + Args: + frame (lldb.SBFrame): The frame to inspect. + + Returns: + list of lldb.SBValue: The recognized arguments, or an empty list + if none could be recognized. + """ + return [] + + def should_hide(self, frame: lldb.SBFrame) -> bool: + """Whether this frame should be hidden when displaying backtraces. + + Args: + frame (lldb.SBFrame): The frame to inspect. + + Returns: + bool: `True` if this frame should be hidden, `False` otherwise. + Defaults to `False`. + """ + return False + + def select_most_relevant_frame(self, frame: lldb.SBFrame) -> Optional[lldb.SBFrame]: + """Pick a diff erent frame to auto-select when the process stops in + this recognized frame. Useful when the recognized frame is trampoline + code that the user probably didn't want to land in (e.g. a language + runtime's exception-throw path). + + Args: + frame (lldb.SBFrame): The recognized frame. + + Returns: + lldb.SBFrame: The frame to select instead, or `None` to keep + the default selection. + """ + return None + + def get_exception(self, frame: lldb.SBFrame) -> Optional[lldb.SBValue]: + """Get the exception value associated with this recognized frame, + if any (e.g. the exception object at a language runtime's throw + site). + + Args: + frame (lldb.SBFrame): The recognized frame. + + Returns: + lldb.SBValue: The exception value, or `None` if this frame + doesn't correspond to an exception. + """ + return None + + def get_stop_description(self, frame: lldb.SBFrame) -> str: + """Get the stop description to surface for this recognized frame, + replacing whatever description lldb would have printed. + + Args: + frame (lldb.SBFrame): The recognized frame. + + Returns: + str: The stop description, or the empty string to keep the + default. + """ + return "" diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h new file mode 100644 index 0000000000000..333973b9f933b --- /dev/null +++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_INTERPRETER_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERINTERFACE_H +#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERINTERFACE_H + +#include "ScriptedInterface.h" +#include "lldb/lldb-private.h" + +namespace lldb_private { +class ScriptedStackFrameRecognizerInterface : virtual public ScriptedInterface { +public: + virtual llvm::Expected<StructuredData::GenericSP> + CreatePluginObject(const ScriptedMetadata &scripted_metadata) = 0; + + virtual lldb::ValueObjectListSP + GetRecognizedArguments(lldb::StackFrameSP frame_sp) { + return lldb::ValueObjectListSP(); + } + + virtual bool ShouldHide(lldb::StackFrameSP frame_sp) { return false; } + + virtual lldb::StackFrameSP + SelectMostRelevantFrame(lldb::StackFrameSP frame_sp) { + return nullptr; + } + + virtual lldb::ValueObjectSP GetException(lldb::StackFrameSP frame_sp) { + return lldb::ValueObjectSP(); + } + + virtual std::string GetStopDescription(lldb::StackFrameSP frame_sp) { + return ""; + } +}; +} // namespace lldb_private + +#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERINTERFACE_H diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h index 2b9cb8f7bb866..122e779bf0279 100644 --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -248,22 +248,6 @@ class ScriptInterpreter : public PluginInterface { return StructuredData::GenericSP(); } - virtual StructuredData::GenericSP - CreateFrameRecognizer(const char *class_name) { - return StructuredData::GenericSP(); - } - - virtual lldb::ValueObjectListSP GetRecognizedArguments( - const StructuredData::ObjectSP &implementor, - lldb::StackFrameSP frame_sp) { - return lldb::ValueObjectListSP(); - } - - virtual bool ShouldHide(const StructuredData::ObjectSP &implementor, - lldb::StackFrameSP frame_sp) { - return false; - } - virtual StructuredData::ObjectSP LoadPluginModule(const FileSpec &file_spec, lldb_private::Status &error) { return StructuredData::ObjectSP(); @@ -572,6 +556,11 @@ class ScriptInterpreter : public PluginInterface { return {}; } + virtual lldb::ScriptedStackFrameRecognizerInterfaceSP + CreateScriptedStackFrameRecognizerInterface() { + return {}; + } + virtual StructuredData::ObjectSP CreateStructuredDataFromScriptObject(ScriptObject obj) { return {}; diff --git a/lldb/include/lldb/Target/StackFrameRecognizer.h b/lldb/include/lldb/Target/StackFrameRecognizer.h index 97a35778ae070..95e8a03cac96c 100644 --- a/lldb/include/lldb/Target/StackFrameRecognizer.h +++ b/lldb/include/lldb/Target/StackFrameRecognizer.h @@ -77,8 +77,7 @@ class StackFrameRecognizer /// tracks a particular Python classobject, which will be asked to recognize /// stack frames. class ScriptedStackFrameRecognizer : public StackFrameRecognizer { - lldb_private::ScriptInterpreter *m_interpreter; - lldb_private::StructuredData::ObjectSP m_python_object_sp; + lldb::ScriptedStackFrameRecognizerInterfaceSP m_interface_sp; std::string m_python_class; diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index b36ad09f2e892..93c252b55de99 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -267,7 +267,8 @@ enum ScriptedExtension { eScriptedExtensionScriptedHook, eScriptedExtensionScriptedThread, eScriptedExtensionScriptedFrame, - kLastScriptedExtension = eScriptedExtensionScriptedFrame + eScriptedExtensionScriptedStackFrameRecognizer, + kLastScriptedExtension = eScriptedExtensionScriptedStackFrameRecognizer }; /// Register numbering types. diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h index 6dbc095f15ef6..157aa5743f016 100644 --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -198,6 +198,7 @@ class ScriptedPlatformInterface; class ScriptedProcessInterface; class ScriptedThreadInterface; class ScriptedThreadPlanInterface; +class ScriptedStackFrameRecognizerInterface; class ScriptedSyntheticChildren; class SearchFilter; class Section; @@ -435,6 +436,8 @@ typedef std::shared_ptr<lldb_private::ScriptedThreadPlanInterface> ScriptedThreadPlanInterfaceSP; typedef std::shared_ptr<lldb_private::ScriptedBreakpointInterface> ScriptedBreakpointInterfaceSP; +typedef std::shared_ptr<lldb_private::ScriptedStackFrameRecognizerInterface> + ScriptedStackFrameRecognizerInterfaceSP; typedef std::shared_ptr<lldb_private::Section> SectionSP; typedef std::unique_ptr<lldb_private::SectionList> SectionListUP; typedef std::weak_ptr<lldb_private::Section> SectionWP; diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp index 8420782b02814..6436d8161583d 100644 --- a/lldb/source/Interpreter/ScriptInterpreter.cpp +++ b/lldb/source/Interpreter/ScriptInterpreter.cpp @@ -212,6 +212,8 @@ ScriptInterpreter::ExtensionToString(lldb::ScriptedExtension extension) { return "ScriptedThread"; case eScriptedExtensionScriptedFrame: return "ScriptedFrame"; + case eScriptedExtensionScriptedStackFrameRecognizer: + return "ScriptedStackFrameRecognizer"; } llvm_unreachable("unhandled ScriptedExtension"); } @@ -230,6 +232,8 @@ ScriptInterpreter::StringToExtension(llvm::StringRef string) { .CaseLower("ScriptedHook", eScriptedExtensionScriptedHook) .CaseLower("ScriptedThread", eScriptedExtensionScriptedThread) .CaseLower("ScriptedFrame", eScriptedExtensionScriptedFrame) + .CaseLower("ScriptedStackFrameRecognizer", + eScriptedExtensionScriptedStackFrameRecognizer) .Default(eScriptedExtensionInvalid); } diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt index fb7d0cb82f8ca..201574da72a19 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt +++ b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt @@ -32,6 +32,7 @@ set(python_plugin_sources Interfaces/ScriptedPythonInterface.cpp Interfaces/ScriptedHookPythonInterface.cpp Interfaces/ScriptedBreakpointPythonInterface.cpp + Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp Interfaces/ScriptedThreadPlanPythonInterface.cpp Interfaces/ScriptedThreadPythonInterface.cpp ) diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp index cdbb624706f83..8914f6b239023 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp @@ -29,6 +29,7 @@ void ScriptInterpreterPythonInterfaces::Initialize() { ScriptedFrameProviderPythonInterface::Initialize(); ScriptedThreadPythonInterface::Initialize(); ScriptedFramePythonInterface::Initialize(); + ScriptedStackFrameRecognizerPythonInterface::Initialize(); } void ScriptInterpreterPythonInterfaces::Terminate() { @@ -41,4 +42,5 @@ void ScriptInterpreterPythonInterfaces::Terminate() { ScriptedFrameProviderPythonInterface::Terminate(); ScriptedThreadPythonInterface::Terminate(); ScriptedFramePythonInterface::Terminate(); + ScriptedStackFrameRecognizerPythonInterface::Terminate(); } diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h index 9f6324d0503f7..03d747e63a592 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h @@ -19,6 +19,7 @@ #include "ScriptedHookPythonInterface.h" #include "ScriptedPlatformPythonInterface.h" #include "ScriptedProcessPythonInterface.h" +#include "ScriptedStackFrameRecognizerPythonInterface.h" #include "ScriptedThreadPlanPythonInterface.h" #include "ScriptedThreadPythonInterface.h" diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp index 4612b1939b5db..61ceb40dd9d32 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp @@ -305,22 +305,57 @@ template <> lldb::ValueObjectListSP ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectListSP>( python::PythonObject &p, Status &error) { - lldb::SBValueList *sb_value_list = reinterpret_cast<lldb::SBValueList *>( - python::LLDBSWIGPython_CastPyObjectToSBValueList(p.get())); - - if (!sb_value_list) { - error = Status::FromErrorStringWithFormat( - "couldn't cast lldb::SBValueList to lldb::ValueObjectListSP"); - return {}; + // Two Python return shapes are accepted here so callers can go through + // Dispatch<ValueObjectListSP>() uniformly: an `SBValueList` wrapper + // (what most extension methods return) and a plain Python `list` of + // `SBValue` (what `get_recognized_arguments` is documented to return). + lldb::ValueObjectListSP out = std::make_shared<ValueObjectList>(); + if (auto *sb_value_list = reinterpret_cast<lldb::SBValueList *>( + python::LLDBSWIGPython_CastPyObjectToSBValueList(p.get()))) { + for (uint32_t i = 0, e = sb_value_list->GetSize(); i < e; ++i) { + SBValue value = sb_value_list->GetValueAtIndex(i); + out->Append(m_interpreter.GetOpaqueTypeFromSBValue(value)); + } + return out; } - lldb::ValueObjectListSP out = std::make_shared<ValueObjectList>(); - for (uint32_t i = 0, e = sb_value_list->GetSize(); i < e; ++i) { - SBValue value = sb_value_list->GetValueAtIndex(i); - out->Append(m_interpreter.GetOpaqueTypeFromSBValue(value)); + // Fallback: a plain Python `list` of `SBValue`. Round-trip through + // `CreateStructuredObject` so we don't touch the `PyList_*` C API + // directly; unknown-shape items surface as `StructuredData::Generic` + // holding their opaque `PyObject*`, which we hand back to SWIG to + // recover the SBValue wrapper. + StructuredData::ObjectSP structured = p.CreateStructuredObject(); + StructuredData::Array *arr = structured ? structured->GetAsArray() : nullptr; + if (arr) { + size_t index = 0; + bool aborted = false; + arr->ForEach([&](StructuredData::Object *item) { + StructuredData::Generic *generic = item ? item->GetAsGeneric() : nullptr; + if (!generic) { + error = Status::FromErrorStringWithFormatv( + "ValueObjectList item at index {0} is not a " + "StructuredData::Generic", + index); + aborted = true; + return false; + } + auto *sb_value = reinterpret_cast<lldb::SBValue *>( + python::LLDBSWIGPython_CastPyObjectToSBValue( + static_cast<PyObject *>(generic->GetValue()))); + if (sb_value) + if (auto valobj_sp = m_interpreter.GetOpaqueTypeFromSBValue(*sb_value)) + out->Append(valobj_sp); + ++index; + return true; + }); + if (aborted) + return {}; + return out; } - return out; + error = Status::FromErrorStringWithFormat( + "couldn't extract ValueObjectList from Python return value"); + return {}; } template <> diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp new file mode 100644 index 0000000000000..2e05928a2ad57 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "../lldb-python.h" + +#include "lldb/Core/PluginManager.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/lldb-enumerations.h" + +#include "../SWIGPythonBridge.h" +#include "../ScriptInterpreterPythonImpl.h" +#include "ScriptedStackFrameRecognizerPythonInterface.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::python; + +ScriptedStackFrameRecognizerPythonInterface:: + ScriptedStackFrameRecognizerPythonInterface( + ScriptInterpreterPythonImpl &interpreter) + : ScriptedStackFrameRecognizerInterface(), + ScriptedPythonInterface(interpreter) {} + +llvm::Expected<StructuredData::GenericSP> +ScriptedStackFrameRecognizerPythonInterface::CreatePluginObject( + const ScriptedMetadata &scripted_metadata) { + return ScriptedPythonInterface::CreatePluginObject(scripted_metadata, + nullptr); +} + +lldb::ValueObjectListSP +ScriptedStackFrameRecognizerPythonInterface::GetRecognizedArguments( + lldb::StackFrameSP frame_sp) { + Status error; + return Dispatch<lldb::ValueObjectListSP>("get_recognized_arguments", error, + frame_sp); +} + +bool ScriptedStackFrameRecognizerPythonInterface::ShouldHide( + lldb::StackFrameSP frame_sp) { + Status error; + StructuredData::ObjectSP obj = Dispatch("should_hide", error, frame_sp); + + if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, + error)) + return false; + + return obj->GetBooleanValue(); +} + +lldb::StackFrameSP +ScriptedStackFrameRecognizerPythonInterface::SelectMostRelevantFrame( + lldb::StackFrameSP frame_sp) { + Status error; + return Dispatch<lldb::StackFrameSP>("select_most_relevant_frame", error, + frame_sp); +} + +lldb::ValueObjectSP ScriptedStackFrameRecognizerPythonInterface::GetException( + lldb::StackFrameSP frame_sp) { + Status error; + return Dispatch<lldb::ValueObjectSP>("get_exception", error, frame_sp); +} + +std::string ScriptedStackFrameRecognizerPythonInterface::GetStopDescription( + lldb::StackFrameSP frame_sp) { + Status error; + StructuredData::ObjectSP obj = + Dispatch("get_stop_description", error, frame_sp); + if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, + error)) + return ""; + return obj->GetStringValue().str(); +} + +void ScriptedStackFrameRecognizerPythonInterface::Initialize() { + const std::vector<llvm::StringRef> ci_usages = { + "frame recognizer add -l <script-name> [-s <shlib> ...] " + "[-n <symbol> ... | -x <symbol-regex>] [-f false] " + "[--mangled-name-preference <mode>]"}; + PluginManager::RegisterPlugin( + GetPluginNameStatic(), + "Recognize a stack frame and provide extra information about it " + "(recognized arguments, exception object, stop description, " + "hidden/most-relevant frame).", + CreateInstance, eScriptedExtensionScriptedStackFrameRecognizer, + eScriptLanguagePython, {ci_usages, {}}); +} + +void ScriptedStackFrameRecognizerPythonInterface::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h new file mode 100644 index 0000000000000..eaf4116ab54c0 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERPYTHONINTERFACE_H +#define LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERPYTHONINTERFACE_H + +#include "lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h" + +#include "ScriptedPythonInterface.h" +namespace lldb_private { + +class ScriptedStackFrameRecognizerPythonInterface + : public ScriptedStackFrameRecognizerInterface, + public ScriptedPythonInterface, + public PluginInterface { +public: + ScriptedStackFrameRecognizerPythonInterface( + ScriptInterpreterPythonImpl &interpreter); + + llvm::Expected<StructuredData::GenericSP> + CreatePluginObject(const ScriptedMetadata &scripted_metadata) override; + + llvm::SmallVector<AbstractMethodRequirement> + GetAbstractMethodRequirements() const override { + return {}; + } + + lldb::ValueObjectListSP + GetRecognizedArguments(lldb::StackFrameSP frame_sp) override; + + bool ShouldHide(lldb::StackFrameSP frame_sp) override; + + lldb::StackFrameSP + SelectMostRelevantFrame(lldb::StackFrameSP frame_sp) override; + + lldb::ValueObjectSP GetException(lldb::StackFrameSP frame_sp) override; + + std::string GetStopDescription(lldb::StackFrameSP frame_sp) override; + + static void Initialize(); + + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { + return "ScriptedStackFrameRecognizerPythonInterface"; + } + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } +}; +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERPYTHONINTERFACE_H diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h index ce9e12dbf7d3d..07e0da1dcf70d 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -214,17 +214,6 @@ class SWIGBridge { const char *session_dictionary_name, const lldb::ProcessSP &process_sp); - static python::PythonObject - LLDBSWIGPython_CreateFrameRecognizer(const char *python_class_name, - const char *session_dictionary_name); - - static PyObject * - LLDBSwigPython_GetRecognizedArguments(PyObject *implementor, - const lldb::StackFrameSP &frame_sp); - - static bool LLDBSwigPython_ShouldHide(PyObject *implementor, - const lldb::StackFrameSP &frame_sp); - static bool LLDBSWIGPythonRunScriptKeywordProcess( const char *python_function_name, const char *session_dictionary_name, const lldb::ProcessSP &process, std::string &output); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp index f3f2b801d43e2..a153563b4534b 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -1624,92 +1624,6 @@ bool ScriptInterpreterPythonImpl::GenerateTypeSynthClass( return true; } -StructuredData::GenericSP -ScriptInterpreterPythonImpl::CreateFrameRecognizer(const char *class_name) { - if (class_name == nullptr || class_name[0] == '\0') - return StructuredData::GenericSP(); - - Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); - PythonObject ret_val = SWIGBridge::LLDBSWIGPython_CreateFrameRecognizer( - class_name, m_dictionary_name.c_str()); - - return StructuredData::GenericSP( - new StructuredPythonObject(std::move(ret_val))); -} - -lldb::ValueObjectListSP ScriptInterpreterPythonImpl::GetRecognizedArguments( - const StructuredData::ObjectSP &os_plugin_object_sp, - lldb::StackFrameSP frame_sp) { - Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); - - if (!os_plugin_object_sp) - return ValueObjectListSP(); - - StructuredData::Generic *generic = os_plugin_object_sp->GetAsGeneric(); - if (!generic) - return nullptr; - - PythonObject implementor(PyRefType::Borrowed, - (PyObject *)generic->GetValue()); - - if (!implementor.IsAllocated()) - return ValueObjectListSP(); - - PythonObject py_return(PyRefType::Owned, - SWIGBridge::LLDBSwigPython_GetRecognizedArguments( - implementor.get(), frame_sp)); - - // if it fails, print the error but otherwise go on - if (PyErr_Occurred()) { - PyErr_Print(); - PyErr_Clear(); - } - if (py_return.get()) { - PythonList result_list(PyRefType::Borrowed, py_return.get()); - ValueObjectListSP result = std::make_shared<ValueObjectList>(); - for (size_t i = 0; i < result_list.GetSize(); i++) { - PyObject *item = result_list.GetItemAtIndex(i).get(); - lldb::SBValue *sb_value_ptr = - (lldb::SBValue *)LLDBSWIGPython_CastPyObjectToSBValue(item); - auto valobj_sp = - SWIGBridge::LLDBSWIGPython_GetValueObjectSPFromSBValue(sb_value_ptr); - if (valobj_sp) - result->Append(valobj_sp); - } - return result; - } - return ValueObjectListSP(); -} - -bool ScriptInterpreterPythonImpl::ShouldHide( - const StructuredData::ObjectSP &os_plugin_object_sp, - lldb::StackFrameSP frame_sp) { - Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); - - if (!os_plugin_object_sp) - return false; - - StructuredData::Generic *generic = os_plugin_object_sp->GetAsGeneric(); - if (!generic) - return false; - - PythonObject implementor(PyRefType::Borrowed, - (PyObject *)generic->GetValue()); - - if (!implementor.IsAllocated()) - return false; - - bool result = - SWIGBridge::LLDBSwigPython_ShouldHide(implementor.get(), frame_sp); - - // if it fails, print the error but otherwise go on - if (PyErr_Occurred()) { - PyErr_Print(); - PyErr_Clear(); - } - return result; -} - ScriptedProcessInterfaceUP ScriptInterpreterPythonImpl::CreateScriptedProcessInterface() { return std::make_unique<ScriptedProcessPythonInterface>(*this); @@ -1725,6 +1639,11 @@ ScriptInterpreterPythonImpl::CreateScriptedBreakpointInterface() { return std::make_shared<ScriptedBreakpointPythonInterface>(*this); } +ScriptedStackFrameRecognizerInterfaceSP +ScriptInterpreterPythonImpl::CreateScriptedStackFrameRecognizerInterface() { + return std::make_shared<ScriptedStackFrameRecognizerPythonInterface>(*this); +} + ScriptedThreadInterfaceSP ScriptInterpreterPythonImpl::CreateScriptedThreadInterface() { return std::make_shared<ScriptedThreadPythonInterface>(*this); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h index 353332a784e60..d8a817198253f 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -76,16 +76,6 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython { StructuredData::ObjectSP CreateStructuredDataFromScriptObject(ScriptObject obj) override; - StructuredData::GenericSP - CreateFrameRecognizer(const char *class_name) override; - - lldb::ValueObjectListSP - GetRecognizedArguments(const StructuredData::ObjectSP &implementor, - lldb::StackFrameSP frame_sp) override; - - bool ShouldHide(const StructuredData::ObjectSP &implementor, - lldb::StackFrameSP frame_sp) override; - lldb::ScriptedProcessInterfaceUP CreateScriptedProcessInterface() override; lldb::ScriptedHookInterfaceSP CreateScriptedHookInterface() override; @@ -93,6 +83,9 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython { lldb::ScriptedBreakpointInterfaceSP CreateScriptedBreakpointInterface() override; + lldb::ScriptedStackFrameRecognizerInterfaceSP + CreateScriptedStackFrameRecognizerInterface() override; + lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() override; lldb::ScriptedFrameInterfaceSP CreateScriptedFrameInterface() override; diff --git a/lldb/source/Target/StackFrameRecognizer.cpp b/lldb/source/Target/StackFrameRecognizer.cpp index f297c71ff44d7..589f3805e3a67 100644 --- a/lldb/source/Target/StackFrameRecognizer.cpp +++ b/lldb/source/Target/StackFrameRecognizer.cpp @@ -8,39 +8,62 @@ #include "lldb/Target/StackFrameRecognizer.h" #include "lldb/Core/Module.h" +#include "lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h" #include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Target/StackFrame.h" #include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/ScriptedMetadata.h" using namespace lldb; using namespace lldb_private; class ScriptedRecognizedStackFrame : public RecognizedStackFrame { bool m_hidden; + lldb::StackFrameSP m_most_relevant_frame; + lldb::ValueObjectSP m_exception; public: - ScriptedRecognizedStackFrame(ValueObjectListSP args, bool hidden) - : m_hidden(hidden) { + ScriptedRecognizedStackFrame(ValueObjectListSP args, bool hidden, + lldb::StackFrameSP most_relevant_frame, + lldb::ValueObjectSP exception, + std::string stop_desc) + : m_hidden(hidden), m_most_relevant_frame(std::move(most_relevant_frame)), + m_exception(std::move(exception)) { m_arguments = std::move(args); + m_stop_desc = std::move(stop_desc); } bool ShouldHide() override { return m_hidden; } + lldb::StackFrameSP GetMostRelevantFrame() override { + return m_most_relevant_frame; + } + lldb::ValueObjectSP GetExceptionObject() override { return m_exception; } }; ScriptedStackFrameRecognizer::ScriptedStackFrameRecognizer( ScriptInterpreter *interpreter, const char *pclass) - : m_interpreter(interpreter), m_python_class(pclass) { - m_python_object_sp = - m_interpreter->CreateFrameRecognizer(m_python_class.c_str()); + : m_python_class(pclass) { + if (!interpreter) + return; + + m_interface_sp = interpreter->CreateScriptedStackFrameRecognizerInterface(); + if (!m_interface_sp) + return; + + ScriptedMetadata scripted_metadata(m_python_class, nullptr); + auto obj_or_err = m_interface_sp->CreatePluginObject(scripted_metadata); + if (!obj_or_err) { + llvm::consumeError(obj_or_err.takeError()); + m_interface_sp.reset(); + } } RecognizedStackFrameSP ScriptedStackFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) { - if (!m_python_object_sp || !m_interpreter) + if (!m_interface_sp) return RecognizedStackFrameSP(); - ValueObjectListSP args = - m_interpreter->GetRecognizedArguments(m_python_object_sp, frame); + ValueObjectListSP args = m_interface_sp->GetRecognizedArguments(frame); auto args_synthesized = std::make_shared<ValueObjectList>(); if (args) { for (const auto &o : args->GetObjects()) @@ -48,10 +71,15 @@ ScriptedStackFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) { *o, eValueTypeVariableArgument)); } - bool hidden = m_interpreter->ShouldHide(m_python_object_sp, frame); + bool hidden = m_interface_sp->ShouldHide(frame); + lldb::StackFrameSP most_relevant = + m_interface_sp->SelectMostRelevantFrame(frame); + lldb::ValueObjectSP exception = m_interface_sp->GetException(frame); + std::string stop_desc = m_interface_sp->GetStopDescription(frame); - return RecognizedStackFrameSP( - new ScriptedRecognizedStackFrame(args_synthesized, hidden)); + return RecognizedStackFrameSP(new ScriptedRecognizedStackFrame( + args_synthesized, hidden, std::move(most_relevant), std::move(exception), + std::move(stop_desc))); } void StackFrameRecognizerManager::BumpGeneration() { diff --git a/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py b/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py index 68df086143585..9e3142a691c72 100644 --- a/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py +++ b/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py @@ -222,6 +222,46 @@ def test_frame_recognizer_hiding(self): frame = thread.GetSelectedFrame() self.assertIn("main", frame.name) + def test_frame_recognizer_optional_hooks(self): + """Exercise select_most_relevant_frame, get_exception, and + get_stop_description via a recognizer that implements all three. + Each hook records the frame name it saw in a class-level list so + the test can assert the hook actually fired.""" + self.build() + + target, process, thread, _ = lldbutil.run_to_name_breakpoint(self, "nested") + + self.expect("frame recognizer clear") + self.expect( + "command script import " + + os.path.join(self.getSourceDir(), "recognizer.py") + ) + + # Reset the call trackers to isolate this test's activity from + # anything the recognizer might have observed earlier. + import recognizer + + recognizer.NestedFrameRecognizer.select_most_relevant_frame_calls.clear() + recognizer.NestedFrameRecognizer.get_exception_calls.clear() + recognizer.NestedFrameRecognizer.get_stop_description_calls.clear() + + self.expect( + "frame recognizer add -l recognizer.NestedFrameRecognizer " + "-f false -s a.out -n nested" + ) + + # Backtracing triggers recognition on each frame lldb walks. + self.runCmd("thread backtrace") + + self.assertIn( + "nested", recognizer.NestedFrameRecognizer.select_most_relevant_frame_calls + ) + self.assertIn("nested", recognizer.NestedFrameRecognizer.get_exception_calls) + self.assertIn( + "nested", + recognizer.NestedFrameRecognizer.get_stop_description_calls, + ) + def test_frame_recognizer_multi_symbol(self): self.build() exe = self.getBuildArtifact("a.out") diff --git a/lldb/test/API/commands/frame/recognizer/recognizer.py b/lldb/test/API/commands/frame/recognizer/recognizer.py index 98666b720b1e2..e7ab03e18bbc4 100644 --- a/lldb/test/API/commands/frame/recognizer/recognizer.py +++ b/lldb/test/API/commands/frame/recognizer/recognizer.py @@ -3,7 +3,7 @@ import lldb -class MyFrameRecognizer(object): +class MyFrameRecognizer: def get_recognized_arguments(self, frame): if frame.name == "foo": arg1 = frame.EvaluateExpression("$arg1").signed @@ -33,11 +33,38 @@ def get_recognized_arguments(self, frame): return [] -class MyOtherFrameRecognizer(object): +class MyOtherFrameRecognizer: def get_recognized_arguments(self, frame): return [] -class BazFrameRecognizer(object): +class BazFrameRecognizer: def should_hide(self, frame): return "baz" in frame.name + + +class NestedFrameRecognizer: + """Exercises the optional hooks beyond `should_hide`: replace the + stop description, redirect the selected frame, and expose a synthetic + exception object. Tracks invocation via class-level lists so a test + can assert each hook actually fires. + """ + + select_most_relevant_frame_calls = [] + get_exception_calls = [] + get_stop_description_calls = [] + + def select_most_relevant_frame(self, frame): + self.select_most_relevant_frame_calls.append(frame.name) + # Redirect from `nested` (frame 0) to its caller `baz` (frame 1). + return frame.thread.frames[1] if frame.thread.num_frames > 1 else None + + def get_exception(self, frame): + self.get_exception_calls.append(frame.name) + return frame.thread.process.target.CreateValueFromExpression( + "recognized_exception", "42" + ) + + def get_stop_description(self, frame): + self.get_stop_description_calls.append(frame.name) + return "recognized nested()" diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp index 1ed6bee384a84..c9298191ec3c1 100644 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -260,18 +260,6 @@ lldb_private::python::SWIGBridge::LLDBSWIGPythonCreateOSPlugin( return python::PythonObject(); } -python::PythonObject -lldb_private::python::SWIGBridge::LLDBSWIGPython_CreateFrameRecognizer( - const char *python_class_name, const char *session_dictionary_name) { - return python::PythonObject(); -} - -PyObject * -lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArguments( - PyObject *implementor, const lldb::StackFrameSP &frame_sp) { - return nullptr; -} - bool lldb_private::python::SWIGBridge::LLDBSWIGPythonRunScriptKeywordProcess( const char *python_function_name, const char *session_dictionary_name, const lldb::ProcessSP &process, std::string &output) { _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
