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

>From c2bdbcd332b71a146b4b13d257ef0ac7bb144324 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Thu, 7 May 2026 17:33:30 +0100
Subject: [PATCH] [lldb][windows] add hidden frame recognizers

---
 .../CPlusPlus/CPPLanguageRuntime.cpp          |  73 +++++++++
 lldb/source/Target/StackFrameRecognizer.cpp   |  15 +-
 .../cpp/msvcstl-internals-recognizer/Makefile |   3 +
 .../TestMSVCSTLInternalsRecognizer.py         | 148 ++++++++++++++++++
 .../cpp/msvcstl-internals-recognizer/main.cpp |   9 ++
 .../TestHiddenFrameMarkers.py                 |   6 -
 6 files changed, 243 insertions(+), 11 deletions(-)
 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..0f760de2c496d 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
@@ -109,6 +109,73 @@ class LibCXXFrameRecognizer : public StackFrameRecognizer {
   }
 };
 
+/// A frame recognizer that hides 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; }
+  };
+
+  // The MSVC demangler emits demangled names that include the return type
+  // (e.g. `void std::_Func_impl_no_alloc<...>::_Do_call`). Detect whether a
+  // function name belongs to the `std::` namespace, accounting for that
+  // optional return-type prefix.
+  static bool IsInStdNamespace(llvm::StringRef name) {
+    return name.starts_with("std::") || name.contains(" std::");
+  }
+
+public:
+  MSVCSTLFrameRecognizer()
+      // Examples of MSVC STL internals that should be hidden:
+      //   void std::_Func_impl_no_alloc<`lambda...',void>::_Do_call
+      //   void std::_Func_class<void>::operator()
+      //   std::_Invoker_ret<std::_Unforced,1>::_Call<...>
+      // The regex is intentionally not anchored: the MSVC demangler may
+      // prepend a return type before the qualified name.
+      : 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 (IsInStdNamespace(
+            parent_sc.function->GetNameNoArguments().GetStringRef()))
+      return m_hidden_frame;
+
+    return {};
+  }
+};
+
 CPPLanguageRuntime::CPPLanguageRuntime(Process *process)
     : LanguageRuntime(process), m_itanium_runtime(process) {
   if (process) {
@@ -118,6 +185,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/source/Target/StackFrameRecognizer.cpp 
b/lldb/source/Target/StackFrameRecognizer.cpp
index 3339195ddcbf0..b832e09df2439 100644
--- a/lldb/source/Target/StackFrameRecognizer.cpp
+++ b/lldb/source/Target/StackFrameRecognizer.cpp
@@ -146,11 +146,11 @@ 
StackFrameRecognizerManager::GetRecognizerForFrame(StackFrameSP frame) {
   if (!module_sp)
     return StackFrameRecognizerSP();
   ConstString module_name = module_sp->GetFileSpec().GetFilename();
+  // We need either a Symbol or a Function to look up a recognizer. On Windows,
+  // when using PDB Symbol is not always populated even when a Function is.
   const Symbol *symbol = symctx.symbol;
-  if (!symbol)
+  if (!symbol && !symctx.function)
     return StackFrameRecognizerSP();
-  Address start_addr = symbol->GetAddress();
-  Address current_addr = frame->GetFrameCodeAddress();
 
   for (const auto &entry : m_recognizers) {
     if (!entry.enabled)
@@ -174,9 +174,14 @@ 
StackFrameRecognizerManager::GetRecognizerForFrame(StackFrameSP frame) {
       if (!entry.symbol_regexp->Execute(function_name.GetStringRef()))
         continue;
 
-    if (entry.first_instruction_only)
-      if (start_addr != current_addr)
+    if (entry.first_instruction_only) {
+      // First-instruction matching requires a Symbol to know the function's
+      // entry address. Without one, we cannot apply this recognizer here.
+      if (!symbol)
         continue;
+      if (symbol->GetAddress() != frame->GetFrameCodeAddress())
+        continue;
+    }
 
     return entry.recognizer;
   }
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..417b2780d0cd4
--- /dev/null
+++ 
b/lldb/test/API/lang/cpp/msvcstl-internals-recognizer/TestMSVCSTLInternalsRecognizer.py
@@ -0,0 +1,148 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+def _qualified_name_in_std(name):
+    """Return True if `name` is a function in the `std::` namespace.
+
+    Handles the MSVC demangler convention of prefixing the demangled name
+    with the return type (e.g. `void std::_Func_class<void>::operator()`).
+    """
+    if not name:
+        return False
+    return name.startswith("std::") or " std::" in name
+
+
+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."""
+        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(
+                _qualified_name_in_std(name),
+                f"hidden frame #{i} '{name}' should be in 'std::' namespace",
+            )
+            parent = thread.GetFrameAtIndex(i + 1)
+            self.assertIsNotNone(parent)
+            parent_name = parent.GetFunctionName() or ""
+            self.assertTrue(
+                _qualified_name_in_std(parent_name),
+                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;
+}
diff --git 
a/lldb/test/API/terminal/hidden_frame_markers/TestHiddenFrameMarkers.py 
b/lldb/test/API/terminal/hidden_frame_markers/TestHiddenFrameMarkers.py
index b3c6627641775..50e648befa65b 100644
--- a/lldb/test/API/terminal/hidden_frame_markers/TestHiddenFrameMarkers.py
+++ b/lldb/test/API/terminal/hidden_frame_markers/TestHiddenFrameMarkers.py
@@ -10,9 +10,6 @@
 
 class HiddenFrameMarkerTest(TestBase):
     @unicode_test
-    @expectedFailureWindows(
-        bugnumber="https://github.com/llvm/llvm-project/issues/191459";
-    )
     def test_hidden_frame_markers(self):
         """Test that hidden frame markers are rendered in backtraces"""
         self.build()
@@ -52,9 +49,6 @@ def test_hidden_frame_markers(self):
         )
 
     @unicode_test
-    @expectedFailureWindows(
-        bugnumber="https://github.com/llvm/llvm-project/issues/191459";
-    )
     def test_nested_hidden_frame_markers(self):
         """Test that nested hidden frame markers are rendered in backtraces"""
         self.build()

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

Reply via email to