https://github.com/charles-zablit created 
https://github.com/llvm/llvm-project/pull/196336

None

>From 29e85dd3171c81214d99e97dd187a6cd842cf3e7 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Thu, 7 May 2026 15:50:06 +0100
Subject: [PATCH] [lldb][windows] add hidden frame recognizers

---
 .../CPlusPlus/CPPLanguageRuntime.cpp          |  63 ++++++++
 .../cpp/msvcstl-internals-recognizer/Makefile |   3 +
 .../TestMSVCSTLInternalsRecognizer.py         | 134 ++++++++++++++++++
 .../cpp/msvcstl-internals-recognizer/main.cpp |   9 ++
 4 files changed, 209 insertions(+)
 create mode 100644 lldb/test/API/lang/cpp/msvcstl-internals-recognizer/Makefile
 create mode 100644 
lldb/test/API/lang/cpp/msvcstl-internals-recognizer/TestMSVCSTLInternalsRecognizer.py
 create mode 100644 lldb/test/API/lang/cpp/msvcstl-internals-recognizer/main.cpp

diff --git 
a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp 
b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
index c517ec8611932..f22a970f04302 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
@@ -109,6 +109,63 @@ class LibCXXFrameRecognizer : public StackFrameRecognizer {
   }
 };
 
+/// A frame recognizer that is installed to hide MSVC STL implementation
+/// details from the backtrace. MSVC STL reserves identifiers beginning with an
+/// underscore followed by an uppercase letter (e.g. `_Func_class`) for
+/// implementation details, so frames whose function name starts with `std::_`
+/// followed by an uppercase letter are hidden when called from within `std::`.
+class MSVCSTLFrameRecognizer : public StackFrameRecognizer {
+  RegularExpression m_hidden_regex;
+  RecognizedStackFrameSP m_hidden_frame;
+
+  struct MSVCSTLHiddenFrame : public RecognizedStackFrame {
+    bool ShouldHide() override { return true; }
+  };
+
+public:
+  MSVCSTLFrameRecognizer()
+      // Examples of MSVC STL internals that should be hidden:
+      //   std::_Func_impl_no_alloc<`lambda...',void>::_Do_call
+      //   std::_Func_class<void>::operator()
+      //   std::_Invoker_ret<std::_Unforced,1>::_Call<...>
+      : m_hidden_regex(R"(^std::_[A-Z])"),
+        m_hidden_frame(new MSVCSTLHiddenFrame()) {}
+
+  std::string GetName() override { return "MSVC STL frame recognizer"; }
+
+  lldb::RecognizedStackFrameSP
+  RecognizeFrame(lldb::StackFrameSP frame_sp) override {
+    if (!frame_sp)
+      return {};
+    const auto &sc = frame_sp->GetSymbolContext(lldb::eSymbolContextFunction);
+    if (!sc.function)
+      return {};
+
+    if (!m_hidden_regex.Execute(sc.function->GetNameNoArguments()))
+      return {};
+
+    // Only hide this frame if the immediate caller is also within MSVC STL.
+    // This keeps the outermost std-facing frame (the one called by user code)
+    // visible in the backtrace.
+    lldb::ThreadSP thread_sp = frame_sp->GetThread();
+    if (!thread_sp)
+      return {};
+    lldb::StackFrameSP parent_frame_sp =
+        thread_sp->GetStackFrameAtIndex(frame_sp->GetFrameIndex() + 1);
+    if (!parent_frame_sp)
+      return {};
+    const auto &parent_sc =
+        parent_frame_sp->GetSymbolContext(lldb::eSymbolContextFunction);
+    if (!parent_sc.function)
+      return {};
+    if (parent_sc.function->GetNameNoArguments().GetStringRef().starts_with(
+            "std::"))
+      return m_hidden_frame;
+
+    return {};
+  }
+};
+
 CPPLanguageRuntime::CPPLanguageRuntime(Process *process)
     : LanguageRuntime(process), m_itanium_runtime(process) {
   if (process) {
@@ -118,6 +175,12 @@ CPPLanguageRuntime::CPPLanguageRuntime(Process *process)
         /*mangling_preference=*/Mangled::ePreferDemangledWithoutArguments,
         /*first_instruction_only=*/false);
 
+    process->GetTarget().GetFrameRecognizerManager().AddRecognizer(
+        StackFrameRecognizerSP(new MSVCSTLFrameRecognizer()), {},
+        std::make_shared<RegularExpression>("^std::_[A-Z]"),
+        /*mangling_preference=*/Mangled::ePreferDemangledWithoutArguments,
+        /*first_instruction_only=*/false);
+
     RegisterVerboseTrapFrameRecognizer(*process);
   }
 }
diff --git a/lldb/test/API/lang/cpp/msvcstl-internals-recognizer/Makefile 
b/lldb/test/API/lang/cpp/msvcstl-internals-recognizer/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/msvcstl-internals-recognizer/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git 
a/lldb/test/API/lang/cpp/msvcstl-internals-recognizer/TestMSVCSTLInternalsRecognizer.py
 
b/lldb/test/API/lang/cpp/msvcstl-internals-recognizer/TestMSVCSTLInternalsRecognizer.py
new file mode 100644
index 0000000000000..de282b180a4f5
--- /dev/null
+++ 
b/lldb/test/API/lang/cpp/msvcstl-internals-recognizer/TestMSVCSTLInternalsRecognizer.py
@@ -0,0 +1,134 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class MSVCSTLInternalsRecognizerTestCase(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def _run_to_target(self):
+        self.build()
+        return lldbutil.run_to_source_breakpoint(
+            self, "break here", lldb.SBFileSpec("main.cpp")
+        )
+
+    @skipUnlessWindows
+    def test_frame_recognizer(self):
+        """At least one MSVC STL internal frame between `target` and `main`
+        should be hidden, but not all frames."""
+        (target, process, thread, bkpt) = self._run_to_target()
+
+        self.assertIn("target", thread.GetFrameAtIndex(0).GetFunctionName())
+
+        num_hidden = sum(1 for frame in thread.frames if frame.IsHidden())
+        self.assertGreater(num_hidden, 0)
+        self.assertLess(num_hidden, thread.GetNumFrames())
+
+    @skipUnlessWindows
+    def test_outermost_std_frame_visible(self):
+        """The outermost `std::*` frame (called directly by user code) must
+        stay visible — only frames whose immediate caller is also `std::*`
+        should be hidden."""
+        (target, process, thread, bkpt) = self._run_to_target()
+
+        for i in range(thread.GetNumFrames()):
+            frame = thread.GetFrameAtIndex(i)
+            if not frame.IsHidden():
+                continue
+            name = frame.GetFunctionName() or ""
+            self.assertTrue(
+                name.startswith("std::_"),
+                f"hidden frame #{i} '{name}' should start with 'std::_'",
+            )
+            parent = thread.GetFrameAtIndex(i + 1)
+            self.assertIsNotNone(parent)
+            parent_name = parent.GetFunctionName() or ""
+            self.assertTrue(
+                parent_name.startswith("std::"),
+                f"hidden frame #{i} '{name}' has non-std parent 
'{parent_name}'",
+            )
+
+    @skipUnlessWindows
+    def test_backtrace(self):
+        """`bt` hides MSVC STL internals; `bt -u` shows them."""
+        (target, process, thread, bkpt) = self._run_to_target()
+
+        self.expect(
+            "thread backtrace",
+            ordered=True,
+            substrs=["frame", "target", "frame", "main"],
+        )
+        self.expect(
+            "thread backtrace",
+            matching=False,
+            patterns=[r"frame.*std::_Func_impl", r"frame.*_Do_call"],
+        )
+        self.expect(
+            "thread backtrace -u",
+            ordered=True,
+            patterns=[r"frame.*target", r"frame.*std::_", r"frame.*main"],
+        )
+        self.expect(
+            "bt -u",
+            ordered=True,
+            patterns=[r"frame.*target", r"frame.*std::_", r"frame.*main"],
+        )
+        self.expect(
+            "thread backtrace --unfiltered",
+            ordered=True,
+            patterns=[r"frame.*target", r"frame.*std::_", r"frame.*main"],
+        )
+
+    @skipUnlessWindows
+    def test_up_down(self):
+        """`up` and `down` should skip past hidden MSVC STL frames."""
+        (target, process, thread, bkpt) = self._run_to_target()
+
+        frame = thread.selected_frame
+        self.assertIn("target", frame.GetFunctionName())
+        start_idx = frame.GetFrameID()
+
+        # Walk up until we hit `main`. The number of `up` invocations must be
+        # less than the raw frame distance, proving hidden frames were skipped.
+        up_steps = 0
+        for _ in range(thread.GetNumFrames()):
+            self.expect("up")
+            up_steps += 1
+            frame = thread.selected_frame
+            if frame.GetFunctionName() == "main":
+                break
+        end_idx = frame.GetFrameID()
+        self.assertEqual(frame.GetFunctionName(), "main")
+        self.assertLess(up_steps, end_idx - start_idx, "expected skipped 
frames going up")
+
+        # Walk back down to `target`.
+        start_idx = frame.GetFrameID()
+        down_steps = 0
+        for _ in range(thread.GetNumFrames()):
+            self.expect("down")
+            down_steps += 1
+            frame = thread.selected_frame
+            if "target" in (frame.GetFunctionName() or ""):
+                break
+        end_idx = frame.GetFrameID()
+        self.assertIn("target", frame.GetFunctionName())
+        self.assertLess(down_steps, start_idx - end_idx, "expected skipped 
frames going down")
+
+    @skipUnlessWindows
+    def test_user_lambda_not_hidden(self):
+        """The user's lambda wrapper (`main::<lambda_...>::operator()`)
+        is user code and must NOT be hidden, even though it sits among
+        `std::function` machinery."""
+        (target, process, thread, bkpt) = self._run_to_target()
+
+        for i in range(thread.GetNumFrames()):
+            frame = thread.GetFrameAtIndex(i)
+            name = frame.GetFunctionName() or ""
+            if "lambda" in name and "::operator()" in name:
+                self.assertFalse(
+                    frame.IsHidden(),
+                    f"user lambda frame '{name}' should not be hidden",
+                )
+                return
+        self.fail("did not find a user lambda frame in the backtrace")
diff --git a/lldb/test/API/lang/cpp/msvcstl-internals-recognizer/main.cpp 
b/lldb/test/API/lang/cpp/msvcstl-internals-recognizer/main.cpp
new file mode 100644
index 0000000000000..b1328a1325c75
--- /dev/null
+++ b/lldb/test/API/lang/cpp/msvcstl-internals-recognizer/main.cpp
@@ -0,0 +1,9 @@
+#include <functional>
+
+static void target() { __builtin_printf("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

Reply via email to