Author: Charles Zablit
Date: 2026-02-16T15:49:34+01:00
New Revision: 5d5301dc0cc4a84fb91f103e4afec62de4622e33

URL: 
https://github.com/llvm/llvm-project/commit/5d5301dc0cc4a84fb91f103e4afec62de4622e33
DIFF: 
https://github.com/llvm/llvm-project/commit/5d5301dc0cc4a84fb91f103e4afec62de4622e33.diff

LOG: [lldb] add a marker around hidden frames (#181143)

This is a reland of https://github.com/llvm/llvm-project/pull/167550.
Instead of relying on libcpp for testing, we emulate our own hidden
frames. This was originally causing tests failures on Windows.

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 87a57f7f1a538..a38caa7ac594e 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -347,6 +347,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 c114cd2a13fdf..fe42be3587433 100644
--- a/lldb/include/lldb/Target/StackFrame.h
+++ b/lldb/include/lldb/Target/StackFrame.h
@@ -368,7 +368,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.
   ///
@@ -405,7 +405,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 715781abb83a3..f8822a8dadc9b 100644
--- a/lldb/include/lldb/Target/StackFrameList.h
+++ b/lldb/include/lldb/Target/StackFrameList.h
@@ -50,6 +50,23 @@ 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 GetFrameMarker(lldb::StackFrameSP frame_sp,
+                             lldb::StackFrameSP selected_frame_sp,
+                             bool show_hidden_marker);
+
   /// 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
@@ -97,7 +114,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 df0b2cba4c573..b4658b149af90 100644
--- a/lldb/packages/Python/lldbsuite/test/decorators.py
+++ b/lldb/packages/Python/lldbsuite/test/decorators.py
@@ -442,6 +442,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 698f282488f72..c9141959a50f2 100644
--- a/lldb/source/Core/CoreProperties.td
+++ b/lldb/source/Core/CoreProperties.td
@@ -129,6 +129,10 @@ let Definition = "debugger", Path = "" 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 00dea7da3497e..12f8039da947e 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -612,6 +612,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 a4bdc20410007..ec3c3a9b32010 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -1946,7 +1946,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;
 
@@ -2045,7 +2045,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 b46916a9af35e..5182eb6151b23 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>
 
@@ -948,11 +949,45 @@ 
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::GetFrameMarker(lldb::StackFrameSP frame_sp,
+                                           lldb::StackFrameSP 
selected_frame_sp,
+                                           bool show_hidden_marker) {
+  bool show_unicode_marker = Terminal::SupportsUnicode() && show_hidden_marker;
+  if (frame_sp == selected_frame_sp)
+    return show_unicode_marker ? u8" * " : u8"* ";
+  if (!show_unicode_marker)
+    return u8"  ";
+  if (IsPreviousFrameHidden(*frame_sp))
+    return u8"﹉ ";
+  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)
@@ -970,25 +1005,18 @@ 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 = GetFrameMarker(frame_sp, selected_frame_sp, show_hidden_marker);
+    else
+      marker = GetFrameMarker(frame_sp, /*selected_frame_sp=*/nullptr,
+                              show_hidden_marker);
 
     // Hide uninteresting frames unless it's the selected frame.
     if (!show_hidden && frame_sp != selected_frame_sp && frame_sp->IsHidden())

diff  --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp
index 44b664ac70d29..aa005a2b9b3db 100644
--- a/lldb/source/Target/Thread.cpp
+++ b/lldb/source/Target/Thread.cpp
@@ -1961,9 +1961,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;
@@ -1997,16 +1997,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();
@@ -2106,9 +2109,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..50e648befa65b
--- /dev/null
+++ b/lldb/test/API/terminal/hidden_frame_markers/TestHiddenFrameMarkers.py
@@ -0,0 +1,157 @@
+"""
+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 first", lldb.SBFileSpec("main.cpp")
+        )
+        self.expect(
+            "bt",
+            substrs=[
+                "   * frame #0:",
+                "  ﹉ frame #2:",
+                "     frame #3:",
+                "     frame #4:",
+            ],
+        )
+
+        self.runCmd("f 2")
+        self.expect(
+            "bt",
+            substrs=[
+                "  ﹍ frame #0:",
+                "   * frame #2:",
+                "     frame #3:",
+                "     frame #4:",
+            ],
+        )
+
+        self.runCmd("f 3")
+        self.expect(
+            "bt",
+            substrs=[
+                "  ﹍ frame #0:",
+                "  ﹉ frame #2:",
+                "   * frame #3:",
+                "     frame #4:",
+            ],
+        )
+
+    @unicode_test
+    def test_nested_hidden_frame_markers(self):
+        """Test that nested hidden frame markers are rendered in backtraces"""
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here after", lldb.SBFileSpec("main.cpp")
+        )
+        self.expect(
+            "bt",
+            substrs=[
+                "   * frame #0:",
+                "  ﹉ frame #2:",
+                "  ﹍ frame #3:",
+                "  ﹉ frame #5:",
+                "     frame #6:",
+            ],
+        )
+
+        self.runCmd("f 2")
+        self.expect(
+            "bt",
+            substrs=[
+                "  ﹍ frame #0:",
+                "   * frame #2:",
+                "  ﹍ frame #3:",
+                "  ﹉ frame #5:",
+                "     frame #6:",
+            ],
+        )
+
+        self.runCmd("f 3")
+        self.expect(
+            "bt",
+            substrs=[
+                "  ﹍ frame #0:",
+                "  ﹉ frame #2:",
+                "   * frame #3:",
+                "  ﹉ frame #5:",
+                "     frame #6:",
+            ],
+        )
+
+        self.runCmd("f 5")
+        self.expect(
+            "bt",
+            substrs=[
+                "  ﹍ frame #0:",
+                "  ﹉ frame #2:",
+                "  ﹍ frame #3:",
+                "   * frame #5:",
+                "     frame #6:",
+            ],
+        )
+
+        self.runCmd("f 6")
+        self.expect(
+            "bt",
+            substrs=[
+                "  ﹍ frame #0:",
+                "  ﹉ frame #2:",
+                "  ﹍ frame #3:",
+                "  ﹉ frame #5:",
+                "   * frame #6:",
+            ],
+        )
+
+    def test_deactivated_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 first", lldb.SBFileSpec("main.cpp")
+        )
+        self.expect(
+            "bt",
+            substrs=[
+                "  * frame #0:",
+                "    frame #2:",
+                "    frame #3:",
+                "    frame #4:",
+            ],
+        )
+
+        self.runCmd("f 2")
+        self.expect(
+            "bt",
+            substrs=[
+                "    frame #0:",
+                "  * frame #2:",
+                "    frame #3:",
+                "    frame #4:",
+            ],
+        )
+
+        self.runCmd("f 3")
+        self.expect(
+            "bt",
+            substrs=[
+                "    frame #0:",
+                "    frame #2:",
+                "  * frame #3:",
+                "    frame #4:",
+            ],
+        )

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..0523d3cacf581
--- /dev/null
+++ b/lldb/test/API/terminal/hidden_frame_markers/main.cpp
@@ -0,0 +1,25 @@
+void foo();
+void bar();
+
+namespace std {
+namespace __1 {
+void __test_hidden_frame() { foo(); }
+void __test_nested_hidden_frame() { bar(); }
+
+void outer_function() { __test_hidden_frame(); }
+void other_outer_function() { __test_nested_hidden_frame(); }
+} // namespace __1
+} // namespace std
+
+void foo() {
+  std::__1::other_outer_function(); // break here first
+}
+
+void bar() {
+  int a = 0; // break here after
+}
+
+int main() {
+  std::__1::outer_function();
+  return 0;
+}


        
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to