Author: Charles Zablit Date: 2026-01-19T11:43:45+01:00 New Revision: e43331f94a594421180ee783b5b92e5dea70f4c9
URL: https://github.com/llvm/llvm-project/commit/e43331f94a594421180ee783b5b92e5dea70f4c9 DIFF: https://github.com/llvm/llvm-project/commit/e43331f94a594421180ee783b5b92e5dea70f4c9.diff LOG: [lldb] add a marker before hidden frames (#167550) **This patch adds a marker to make hidden frames more explicit.** --- Hidden frames can be confusing for some users, who see that the indexes of the frames in a backtrace are not contiguous. This patch aims to lessen the confusion by adding a delimiter for the first and last non hidden frame, i.e the boundaries. IDE's like Xcode and VSCode represent those in the UI by having the hidden frames either greyed out or collapsed. It's not possible to do this in the CLI, therefore, this patch makes use of 2 unicode characters to mark the beginning and end of the hidden frames range. This patch depends on: - https://github.com/llvm/llvm-project/pull/168603 # Examples In the example below, frame `#2` to `#7` are is hidden, and therefore, frame `#1` is the first non hidden frame of the range while frame `#8` is the last non hidden frame: <img width="488" height="112" alt="Screenshot 2025-11-18 at 18 41 11" src="https://github.com/user-attachments/assets/a21431da-9729-4cf0-a6bc-024aa306fc45" /> If the selected frame is one of the 2 boundary frames, we replace the delimiter character with the select character (`*`). <img width="487" height="111" alt="Screenshot 2025-11-18 at 18 41 03" src="https://github.com/user-attachments/assets/5616fa81-6db6-457d-9d1e-bbe46e710c26" /> <img width="488" height="111" alt="Screenshot 2025-11-18 at 18 40 55" src="https://github.com/user-attachments/assets/93dfa6cf-0956-4718-b31c-f965ec72b56d" /> Added: lldb/test/API/terminal/hidden_frame_markers/Makefile lldb/test/API/terminal/hidden_frame_markers/TestHiddenFrameMarkers.py lldb/test/API/terminal/hidden_frame_markers/main.cpp Modified: lldb/include/lldb/Core/Debugger.h lldb/include/lldb/Target/StackFrame.h lldb/include/lldb/Target/StackFrameList.h lldb/packages/Python/lldbsuite/test/decorators.py lldb/source/Core/CoreProperties.td lldb/source/Core/Debugger.cpp lldb/source/Target/StackFrame.cpp lldb/source/Target/StackFrameList.cpp lldb/source/Target/Thread.cpp Removed: ################################################################################ diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h index a39413c06340c..dadccaeb5d17b 100644 --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -340,6 +340,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>, bool SetUseSourceCache(bool use_source_cache); + bool GetMarkHiddenFrames() const; + bool GetHighlightSource() const; lldb::StopShowColumn GetStopShowColumn() const; diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h index 46922448d6e59..0d07515bf0f13 100644 --- a/lldb/include/lldb/Target/StackFrame.h +++ b/lldb/include/lldb/Target/StackFrame.h @@ -363,7 +363,7 @@ class StackFrame : public ExecutionContextScope, /// \param [in] frame_marker /// Optional string that will be prepended to the frame output description. virtual void DumpUsingSettingsFormat(Stream *strm, bool show_unique = false, - const char *frame_marker = nullptr); + const llvm::StringRef frame_marker = ""); /// Print a description for this frame using a default format. /// @@ -400,7 +400,7 @@ class StackFrame : public ExecutionContextScope, /// Returns true if successful. virtual bool GetStatus(Stream &strm, bool show_frame_info, bool show_source, bool show_unique = false, - const char *frame_marker = nullptr); + const llvm::StringRef frame_marker = ""); /// Query whether this frame is a concrete frame on the call stack, or if it /// is an inlined frame derived from the debug information and presented by diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h index c096fe3ff61a0..c54df941e3aa9 100644 --- a/lldb/include/lldb/Target/StackFrameList.h +++ b/lldb/include/lldb/Target/StackFrameList.h @@ -49,6 +49,22 @@ class StackFrameList : public std::enable_shared_from_this<StackFrameList> { /// Resets the selected frame index of this object. void ClearSelectedFrameIndex(); + /// Returns \p true if the next frame is hidden. + bool IsNextFrameHidden(lldb_private::StackFrame &frame); + + /// Returns \p true if the previous frame is hidden. + bool IsPreviousFrameHidden(lldb_private::StackFrame &frame); + + /// Returns the stack frame marker depending on if \p frame_sp: + /// @li is selected: * + /// @li is the first non hidden frame: ﹍ + /// @li is the last non hidden frame: ﹉ + /// + /// If the terminal does not support Unicode rendering, the hidden frame + /// markers are replaced with whitespaces. + std::string FrameMarker(lldb::StackFrameSP frame_sp, + lldb::StackFrameSP selected_frame_sp); + /// Get the currently selected frame index. /// We should only call SelectMostRelevantFrame if (a) the user hasn't already /// selected a frame, and (b) if this really is a user facing @@ -96,7 +112,8 @@ class StackFrameList : public std::enable_shared_from_this<StackFrameList> { size_t GetStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames, bool show_frame_info, uint32_t num_frames_with_source, bool show_unique = false, bool show_hidden = false, - const char *frame_marker = nullptr); + bool show_hidden_marker = true, + bool show_selected_frame = false); /// Returns whether we have currently fetched all the frames of a stack. bool WereAllFramesFetched() const; diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py index a7df9fe63badc..d7c8ed1da00e0 100644 --- a/lldb/packages/Python/lldbsuite/test/decorators.py +++ b/lldb/packages/Python/lldbsuite/test/decorators.py @@ -439,6 +439,35 @@ def impl(func): return impl +def unicode_test(func): + """Decorate the item as a test which requires Unicode to be enabled. + + lldb checks the value of the `LANG` environment variable for the substring "utf-8" + to determine if the terminal supports Unicode (except on Windows, were we assume + it's always supported). + This decorator sets LANG to `utf-8` before running the test and resets it to its + previous value afterwards. + """ + + def unicode_wrapped(*args, **kwargs): + import os + + previous_lang = os.environ.get("LANG", None) + os.environ["LANG"] = "en_US.UTF-8" + try: + func(*args, **kwargs) + except Exception as err: + raise err + finally: + # Reset the value, whether the test failed or not. + if previous_lang is not None: + os.environ["LANG"] = previous_lang + else: + del os.environ["LANG"] + + return unicode_wrapped + + def no_debug_info_test(func): """Decorate the item as a test what don't use any debug info. If this annotation is specified then the test runner won't generate a separate test for each debug info format.""" diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td index 99bb5a3fc6f73..f39973fdc7a10 100644 --- a/lldb/source/Core/CoreProperties.td +++ b/lldb/source/Core/CoreProperties.td @@ -114,6 +114,10 @@ let Definition = "debugger" in { Global, DefaultTrue, Desc<"If true, LLDB will highlight the displayed source code.">; + def MarkHiddenFrames: Property<"mark-hidden-frames", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, LLDB will add a marker to delimit hidden frames in backtraces.">; def StopShowColumn: Property<"stop-show-column", "Enum">, DefaultEnumValue<"eStopShowColumnAnsiOrCaret">, EnumValues<"OptionEnumValues(s_stop_show_column_values)">, diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index 669dd90cd324f..65acaa5fb9f4d 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -584,6 +584,13 @@ bool Debugger::SetUseSourceCache(bool b) { } return ret; } + +bool Debugger::GetMarkHiddenFrames() const { + const uint32_t idx = ePropertyMarkHiddenFrames; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + bool Debugger::GetHighlightSource() const { const uint32_t idx = ePropertyHighlightSource; return GetPropertyAtIndexAs<bool>( diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp index 340607e14abed..3ef96a46517c9 100644 --- a/lldb/source/Target/StackFrame.cpp +++ b/lldb/source/Target/StackFrame.cpp @@ -1945,7 +1945,7 @@ bool StackFrame::DumpUsingFormat(Stream &strm, } void StackFrame::DumpUsingSettingsFormat(Stream *strm, bool show_unique, - const char *frame_marker) { + const llvm::StringRef frame_marker) { if (strm == nullptr) return; @@ -2044,7 +2044,8 @@ bool StackFrame::HasCachedData() const { } bool StackFrame::GetStatus(Stream &strm, bool show_frame_info, bool show_source, - bool show_unique, const char *frame_marker) { + bool show_unique, + const llvm::StringRef frame_marker) { if (show_frame_info) { strm.Indent(); DumpUsingSettingsFormat(&strm, show_unique, frame_marker); diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index e6112f8f3264b..e0c6aa0542f4d 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -27,6 +27,7 @@ #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "llvm/ADT/SmallPtrSet.h" +#include "llvm/Support/ConvertUTF.h" #include <memory> @@ -929,11 +930,43 @@ StackFrameList::GetStackFrameSPForStackFramePtr(StackFrame *stack_frame_ptr) { return ret_sp; } +bool StackFrameList::IsNextFrameHidden(lldb_private::StackFrame &frame) { + uint32_t frame_idx = frame.GetFrameIndex(); + StackFrameSP frame_sp = GetFrameAtIndex(frame_idx + 1); + if (!frame_sp) + return false; + return frame_sp->IsHidden(); +} + +bool StackFrameList::IsPreviousFrameHidden(lldb_private::StackFrame &frame) { + uint32_t frame_idx = frame.GetFrameIndex(); + if (frame_idx == 0) + return false; + StackFrameSP frame_sp = GetFrameAtIndex(frame_idx - 1); + if (!frame_sp) + return false; + return frame_sp->IsHidden(); +} + +std::string StackFrameList::FrameMarker(lldb::StackFrameSP frame_sp, + lldb::StackFrameSP selected_frame_sp) { + if (frame_sp == selected_frame_sp) + return Terminal::SupportsUnicode() ? u8" * " : u8"* "; + else if (!Terminal::SupportsUnicode()) + return u8" "; + else if (IsPreviousFrameHidden(*frame_sp)) + return u8"﹉ "; + else if (IsNextFrameHidden(*frame_sp)) + return u8"﹍ "; + return u8" "; +} + size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames, bool show_frame_info, uint32_t num_frames_with_source, bool show_unique, bool show_hidden, - const char *selected_frame_marker) { + bool show_hidden_marker, + bool show_selected_frame) { size_t num_frames_displayed = 0; if (num_frames == 0) @@ -951,25 +984,17 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, StackFrameSP selected_frame_sp = m_thread.GetSelectedFrame(DoNoSelectMostRelevantFrame); - const char *unselected_marker = nullptr; std::string buffer; - if (selected_frame_marker) { - size_t len = strlen(selected_frame_marker); - buffer.insert(buffer.begin(), len, ' '); - unselected_marker = buffer.c_str(); - } - const char *marker = nullptr; + std::string marker; for (frame_idx = first_frame; frame_idx < last_frame; ++frame_idx) { frame_sp = GetFrameAtIndex(frame_idx); if (!frame_sp) break; - if (selected_frame_marker != nullptr) { - if (frame_sp == selected_frame_sp) - marker = selected_frame_marker; - else - marker = unselected_marker; - } + if (show_selected_frame) + marker = FrameMarker(frame_sp, selected_frame_sp); + else + marker = FrameMarker(frame_sp, nullptr); // Hide uninteresting frames unless it's the selected frame. if (!show_hidden && frame_sp != selected_frame_sp && frame_sp->IsHidden()) @@ -983,7 +1008,6 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, m_thread.GetID(), num_frames_displayed)) break; - if (!frame_sp->GetStatus(strm, show_frame_info, num_frames_with_source > (first_frame - frame_idx), show_unique, marker)) diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 70d8650662348..1e3c7867eeca1 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -1888,9 +1888,9 @@ size_t Thread::GetStatus(Stream &strm, uint32_t start_frame, uint32_t num_frames, uint32_t num_frames_with_source, bool stop_format, bool show_hidden, bool only_stacks) { + ExecutionContext exe_ctx(shared_from_this()); + Target *target = exe_ctx.GetTargetPtr(); if (!only_stacks) { - ExecutionContext exe_ctx(shared_from_this()); - Target *target = exe_ctx.GetTargetPtr(); Process *process = exe_ctx.GetProcessPtr(); strm.Indent(); bool is_selected = false; @@ -1924,16 +1924,19 @@ size_t Thread::GetStatus(Stream &strm, uint32_t start_frame, const bool show_frame_info = true; const bool show_frame_unique = only_stacks; - const char *selected_frame_marker = nullptr; + bool show_selected_frame = false; if (num_frames == 1 || only_stacks || (GetID() != GetProcess()->GetThreadList().GetSelectedThread()->GetID())) strm.IndentMore(); else - selected_frame_marker = "* "; + show_selected_frame = true; + bool show_hidden_marker = + target && target->GetDebugger().GetMarkHiddenFrames(); num_frames_shown = GetStackFrameList()->GetStatus( strm, start_frame, num_frames, show_frame_info, num_frames_with_source, - show_frame_unique, show_hidden, selected_frame_marker); + show_frame_unique, show_hidden, show_hidden_marker, + show_selected_frame); if (num_frames == 1) strm.IndentLess(); strm.IndentLess(); @@ -2033,9 +2036,13 @@ size_t Thread::GetStackFrameStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames, bool show_frame_info, uint32_t num_frames_with_source, bool show_hidden) { - return GetStackFrameList()->GetStatus(strm, first_frame, num_frames, - show_frame_info, num_frames_with_source, - /*show_unique*/ false, show_hidden); + ExecutionContext exe_ctx(shared_from_this()); + Target *target = exe_ctx.GetTargetPtr(); + bool show_hidden_marker = + target && target->GetDebugger().GetMarkHiddenFrames(); + return GetStackFrameList()->GetStatus( + strm, first_frame, num_frames, show_frame_info, num_frames_with_source, + /*show_unique*/ false, show_hidden, show_hidden_marker); } Unwind &Thread::GetUnwinder() { diff --git a/lldb/test/API/terminal/hidden_frame_markers/Makefile b/lldb/test/API/terminal/hidden_frame_markers/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/terminal/hidden_frame_markers/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/terminal/hidden_frame_markers/TestHiddenFrameMarkers.py b/lldb/test/API/terminal/hidden_frame_markers/TestHiddenFrameMarkers.py new file mode 100644 index 0000000000000..178d97fce17c2 --- /dev/null +++ b/lldb/test/API/terminal/hidden_frame_markers/TestHiddenFrameMarkers.py @@ -0,0 +1,97 @@ +""" +Test that hidden frames are delimited with markers. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class HiddenFrameMarkerTest(TestBase): + @unicode_test + def test_hidden_frame_markers(self): + """Test that hidden frame markers are rendered in backtraces""" + self.build() + lldbutil.run_to_source_breakpoint( + self, "// break here", lldb.SBFileSpec("main.cpp") + ) + self.expect( + "bt", + substrs=[ + " * frame #0:", + " ﹍ frame #1:", + " ﹉ frame #7:", + " frame #8:", + " frame #9:", + ], + ) + + self.runCmd("f 1") + self.expect( + "bt", + substrs=[ + " frame #0:", + " * frame #1:", + " ﹉ frame #7:", + " frame #8:", + " frame #9:", + ], + ) + + self.runCmd("f 7") + self.expect( + "bt", + substrs=[ + " frame #0:", + " ﹍ frame #1:", + " * frame #7:", + " frame #8:", + " frame #9:", + ], + ) + + def test_hidden_frame_markers(self): + """ + Test that hidden frame markers are not rendered in backtraces when + mark-hidden-frames is set to false + """ + self.build() + self.runCmd("settings set mark-hidden-frames 0") + lldbutil.run_to_source_breakpoint( + self, "// break here", lldb.SBFileSpec("main.cpp") + ) + self.expect( + "bt", + substrs=[ + " * frame #0:", + " frame #1:", + " frame #7:", + " frame #8:", + " frame #9:", + ], + ) + + self.runCmd("f 1") + self.expect( + "bt", + substrs=[ + " frame #0:", + " * frame #1:", + " frame #7:", + " frame #8:", + " frame #9:", + ], + ) + + self.runCmd("f 7") + self.expect( + "bt", + substrs=[ + " frame #0:", + " frame #1:", + " * frame #7:", + " frame #8:", + " frame #9:", + ], + ) diff --git a/lldb/test/API/terminal/hidden_frame_markers/main.cpp b/lldb/test/API/terminal/hidden_frame_markers/main.cpp new file mode 100644 index 0000000000000..c0b7e0884538a --- /dev/null +++ b/lldb/test/API/terminal/hidden_frame_markers/main.cpp @@ -0,0 +1,12 @@ +#include <functional> +#include <iostream> + +static void target() { + int a = 0; // break here +} + +int main() { + std::function<void()> fn = [] { target(); }; + fn(); + return 0; +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
