Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lldb] Extend frame recognizers to hide frames from backtraces #104523

Merged
merged 1 commit into from
Aug 20, 2024

Conversation

adrian-prantl
Copy link
Collaborator

Compilers and language runtimes often use helper functions that are fundamentally uninteresting when debugging anything but the compiler/runtime itself. This patch introduces a user-extensible mechanism that allows for these frames to be hidden from backtraces and automatically skipped over when navigating the stack with up and down.

This does not affect the numbering of frames, so f <N> will still provide access to the hidden frames. The bt output will also print a hint that frames have been hidden.

My primary motivation for this feature is to hide thunks in the Swift programming language, but I'm including an example recognizer for std::function::operator() that I wished for myself many times while debugging LLDB.

rdar://126629381

Example output. (Yes, my proof-of-concept recognizer could hide even more frames if we had a method that returned the function name without the return type or I used something that isn't based off regex, but it's really only meant as an example).

before:

(lldb) thread backtrace --filtered=false
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame #1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame #3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()[abi:se200000](this=0x000000016fdff280, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:171:12
    frame #4: 0x00000001000026bc a.out`std::__1::__function::__func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()(this=0x000000016fdff278, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:313:10
    frame #5: 0x0000000100003c38 a.out`std::__1::__function::__value_func<int (int, int)>::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12
    frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame #8: 0x0000000183cdf154 dyld`start + 2476
(lldb) 

after

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame #1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame #8: 0x0000000183cdf154 dyld`start + 2476
Note: Some frames were hidden by frame recognizers

@llvmbot
Copy link
Collaborator

llvmbot commented Aug 15, 2024

@llvm/pr-subscribers-lldb

Author: Adrian Prantl (adrian-prantl)

Changes

Compilers and language runtimes often use helper functions that are fundamentally uninteresting when debugging anything but the compiler/runtime itself. This patch introduces a user-extensible mechanism that allows for these frames to be hidden from backtraces and automatically skipped over when navigating the stack with up and down.

This does not affect the numbering of frames, so f &lt;N&gt; will still provide access to the hidden frames. The bt output will also print a hint that frames have been hidden.

My primary motivation for this feature is to hide thunks in the Swift programming language, but I'm including an example recognizer for std::function::operator() that I wished for myself many times while debugging LLDB.

rdar://126629381

Example output. (Yes, my proof-of-concept recognizer could hide even more frames if we had a method that returned the function name without the return type or I used something that isn't based off regex, but it's really only meant as an example).

before:

(lldb) thread backtrace --filtered=false
* thread #<!-- -->1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #<!-- -->0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame #<!-- -->1: 0x0000000100003a00 a.out`decltype(std::declval&lt;int (*&amp;)(int, int)&gt;()(std::declval&lt;int&gt;(), std::declval&lt;int&gt;())) std::__1::__invoke[abi:se200000]&lt;int (*&amp;)(int, int), int, int&gt;(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame #<!-- -->2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper&lt;int, false&gt;::__call[abi:se200000]&lt;int (*&amp;)(int, int), int, int&gt;(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame #<!-- -->3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func&lt;int (*)(int, int), std::__1::allocator&lt;int (*)(int, int)&gt;, int (int, int)&gt;::operator()[abi:se200000](this=0x000000016fdff280, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:171:12
    frame #<!-- -->4: 0x00000001000026bc a.out`std::__1::__function::__func&lt;int (*)(int, int), std::__1::allocator&lt;int (*)(int, int)&gt;, int (int, int)&gt;::operator()(this=0x000000016fdff278, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:313:10
    frame #<!-- -->5: 0x0000000100003c38 a.out`std::__1::__function::__value_func&lt;int (int, int)&gt;::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12
    frame #<!-- -->6: 0x0000000100002038 a.out`std::__1::function&lt;int (int, int)&gt;::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame #<!-- -->7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame #<!-- -->8: 0x0000000183cdf154 dyld`start + 2476
(lldb) 

after

(lldb) bt
* thread #<!-- -->1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #<!-- -->0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame #<!-- -->1: 0x0000000100003a00 a.out`decltype(std::declval&lt;int (*&amp;)(int, int)&gt;()(std::declval&lt;int&gt;(), std::declval&lt;int&gt;())) std::__1::__invoke[abi:se200000]&lt;int (*&amp;)(int, int), int, int&gt;(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame #<!-- -->2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper&lt;int, false&gt;::__call[abi:se200000]&lt;int (*&amp;)(int, int), int, int&gt;(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame #<!-- -->6: 0x0000000100002038 a.out`std::__1::function&lt;int (int, int)&gt;::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame #<!-- -->7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame #<!-- -->8: 0x0000000183cdf154 dyld`start + 2476
Note: Some frames were hidden by frame recognizers

Patch is 24.25 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/104523.diff

17 Files Affected:

  • (modified) lldb/include/lldb/Target/StackFrameList.h (+1-1)
  • (modified) lldb/include/lldb/Target/StackFrameRecognizer.h (+6-6)
  • (modified) lldb/include/lldb/Target/Thread.h (+3-2)
  • (modified) lldb/source/API/SBThread.cpp (+2-1)
  • (modified) lldb/source/Commands/CommandCompletions.cpp (+2-2)
  • (modified) lldb/source/Commands/CommandObjectFrame.cpp (+26-2)
  • (modified) lldb/source/Commands/CommandObjectMemory.cpp (+2-1)
  • (modified) lldb/source/Commands/CommandObjectThread.cpp (+18-7)
  • (modified) lldb/source/Commands/Options.td (+3)
  • (modified) lldb/source/Core/Debugger.cpp (+2-1)
  • (modified) lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp (+46-1)
  • (modified) lldb/source/Target/Process.cpp (+4-3)
  • (modified) lldb/source/Target/StackFrameList.cpp (+13-2)
  • (modified) lldb/source/Target/Thread.cpp (+8-5)
  • (added) lldb/test/API/lang/cpp/std-function-recognizer/Makefile (+4)
  • (added) lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py (+72)
  • (added) lldb/test/API/lang/cpp/std-function-recognizer/main.cpp (+10)
diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h
index 88e211ff692bd9..5cdca97e910613 100644
--- a/lldb/include/lldb/Target/StackFrameList.h
+++ b/lldb/include/lldb/Target/StackFrameList.h
@@ -91,7 +91,7 @@ class 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_unique = false, bool should_filter = true,
                    const char *frame_marker = nullptr);
 
 protected:
diff --git a/lldb/include/lldb/Target/StackFrameRecognizer.h b/lldb/include/lldb/Target/StackFrameRecognizer.h
index e9ac2750192ef6..dae1c6f4415718 100644
--- a/lldb/include/lldb/Target/StackFrameRecognizer.h
+++ b/lldb/include/lldb/Target/StackFrameRecognizer.h
@@ -28,20 +28,23 @@ namespace lldb_private {
 /// This class provides extra information about a stack frame that was
 /// provided by a specific stack frame recognizer. Right now, this class only
 /// holds recognized arguments (via GetRecognizedArguments).
-
 class RecognizedStackFrame
     : public std::enable_shared_from_this<RecognizedStackFrame> {
 public:
+  virtual ~RecognizedStackFrame() = default;
+
   virtual lldb::ValueObjectListSP GetRecognizedArguments() {
     return m_arguments;
   }
   virtual lldb::ValueObjectSP GetExceptionObject() {
     return lldb::ValueObjectSP();
   }
-  virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; };
-  virtual ~RecognizedStackFrame() = default;
+  virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; }
 
   std::string GetStopDescription() { return m_stop_desc; }
+  /// Controls whether this frame should be filtered out when
+  /// displaying backtraces, for example.
+  virtual bool ShouldHide() { return false; }
 
 protected:
   lldb::ValueObjectListSP m_arguments;
@@ -53,7 +56,6 @@ class RecognizedStackFrame
 /// A base class for frame recognizers. Subclasses (actual frame recognizers)
 /// should implement RecognizeFrame to provide a RecognizedStackFrame for a
 /// given stack frame.
-
 class StackFrameRecognizer
     : public std::enable_shared_from_this<StackFrameRecognizer> {
 public:
@@ -73,7 +75,6 @@ class StackFrameRecognizer
 /// Python implementation for frame recognizers. An instance of this class
 /// 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;
@@ -144,7 +145,6 @@ class StackFrameRecognizerManager {
 /// ValueObject subclass that presents the passed ValueObject as a recognized
 /// value with the specified ValueType. Frame recognizers should return
 /// instances of this class as the returned objects in GetRecognizedArguments().
-
 class ValueObjectRecognizerSynthesizedValue : public ValueObject {
  public:
   static lldb::ValueObjectSP Create(ValueObject &parent, lldb::ValueType type) {
diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h
index aacc59c292ec79..9de58513b160b4 100644
--- a/lldb/include/lldb/Target/Thread.h
+++ b/lldb/include/lldb/Target/Thread.h
@@ -1128,11 +1128,12 @@ class Thread : public std::enable_shared_from_this<Thread>,
 
   size_t GetStatus(Stream &strm, uint32_t start_frame, uint32_t num_frames,
                    uint32_t num_frames_with_source, bool stop_format,
-                   bool only_stacks = false);
+                   bool should_filter, bool only_stacks = false);
 
   size_t GetStackFrameStatus(Stream &strm, uint32_t first_frame,
                              uint32_t num_frames, bool show_frame_info,
-                             uint32_t num_frames_with_source);
+                             uint32_t num_frames_with_source,
+                             bool should_filter);
 
   // We need a way to verify that even though we have a thread in a shared
   // pointer that the object itself is still valid. Currently this won't be the
diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp
index bda981041064ff..7a3d6ff9336159 100644
--- a/lldb/source/API/SBThread.cpp
+++ b/lldb/source/API/SBThread.cpp
@@ -1208,7 +1208,8 @@ bool SBThread::GetStatus(SBStream &status) const {
   ExecutionContext exe_ctx(m_opaque_sp.get(), lock);
 
   if (exe_ctx.HasThreadScope()) {
-    exe_ctx.GetThreadPtr()->GetStatus(strm, 0, 1, 1, true);
+    exe_ctx.GetThreadPtr()->GetStatus(strm, 0, 1, 1, true,
+                                      /*should_filter*/ false);
   } else
     strm.PutCString("No status");
 
diff --git a/lldb/source/Commands/CommandCompletions.cpp b/lldb/source/Commands/CommandCompletions.cpp
index 54f4b368166492..ea647bbfe4c026 100644
--- a/lldb/source/Commands/CommandCompletions.cpp
+++ b/lldb/source/Commands/CommandCompletions.cpp
@@ -791,7 +791,7 @@ void CommandCompletions::ThreadIndexes(CommandInterpreter &interpreter,
   lldb::ThreadSP thread_sp;
   for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {
     StreamString strm;
-    thread_sp->GetStatus(strm, 0, 1, 1, true);
+    thread_sp->GetStatus(strm, 0, 1, 1, true, /*should_filter*/ false);
     request.TryCompleteCurrentArg(std::to_string(thread_sp->GetIndexID()),
                                   strm.GetString());
   }
@@ -835,7 +835,7 @@ void CommandCompletions::ThreadIDs(CommandInterpreter &interpreter,
   lldb::ThreadSP thread_sp;
   for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {
     StreamString strm;
-    thread_sp->GetStatus(strm, 0, 1, 1, true);
+    thread_sp->GetStatus(strm, 0, 1, 1, true, /*should_filter*/ false);
     request.TryCompleteCurrentArg(std::to_string(thread_sp->GetID()),
                                   strm.GetString());
   }
diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp
index 29e460fe3885ff..b93ffe27bb2f29 100644
--- a/lldb/source/Commands/CommandObjectFrame.cpp
+++ b/lldb/source/Commands/CommandObjectFrame.cpp
@@ -278,6 +278,30 @@ class CommandObjectFrameSelect : public CommandObjectParsed {
       if (frame_idx == UINT32_MAX)
         frame_idx = 0;
 
+      // If moving up/down by one, skip over hidden frames.
+      if (*m_options.relative_frame_offset == 1 ||
+          *m_options.relative_frame_offset == -1) {
+        uint32_t candidate_idx = frame_idx;
+        for (unsigned num_try = 0; num_try < 12; ++num_try) {
+          if (candidate_idx == 0 && *m_options.relative_frame_offset == -1) {
+            candidate_idx = UINT32_MAX;
+            break;
+          }
+          candidate_idx += *m_options.relative_frame_offset;
+          if (auto candidate_sp = thread->GetStackFrameAtIndex(candidate_idx)) {
+            if (auto recognized_frame_sp = candidate_sp->GetRecognizedFrame())
+              if (recognized_frame_sp->ShouldHide())
+                continue;
+            // Now candidate_idx is the first non-hidden frame.
+            break;
+          }
+          candidate_idx = UINT32_MAX;
+          break;
+        };
+        if (candidate_idx != UINT32_MAX)
+          m_options.relative_frame_offset = candidate_idx - frame_idx;
+      }
+
       if (*m_options.relative_frame_offset < 0) {
         if (static_cast<int32_t>(frame_idx) >=
             -*m_options.relative_frame_offset)
@@ -318,7 +342,7 @@ class CommandObjectFrameSelect : public CommandObjectParsed {
           }
         }
       }
-    } else {
+      } else {
       if (command.GetArgumentCount() > 1) {
         result.AppendErrorWithFormat(
             "too many arguments; expected frame-index, saw '%s'.\n",
@@ -341,7 +365,7 @@ class CommandObjectFrameSelect : public CommandObjectParsed {
           frame_idx = 0;
         }
       }
-    }
+      }
 
     bool success = thread->SetSelectedFrameByIndexNoisily(
         frame_idx, result.GetOutputStream());
diff --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp
index 137b1ad981073c..baf5d9196e553e 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1570,7 +1570,8 @@ class CommandObjectMemoryHistory : public CommandObjectParsed {
 
     const bool stop_format = false;
     for (auto thread : thread_list) {
-      thread->GetStatus(*output_stream, 0, UINT32_MAX, 0, stop_format);
+      thread->GetStatus(*output_stream, 0, UINT32_MAX, 0, stop_format,
+                        /*should_filter*/ false);
     }
 
     result.SetStatus(eReturnStatusSuccessFinishResult);
diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp
index 605f872a9f45e1..d58c8533f90f7b 100644
--- a/lldb/source/Commands/CommandObjectThread.cpp
+++ b/lldb/source/Commands/CommandObjectThread.cpp
@@ -80,14 +80,20 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
               "invalid integer value for option '%c': %s", short_option,
               option_arg.data());
         break;
-      case 'e': {
+      case 'e':
+      case 'f': {
         bool success;
-        m_extended_backtrace =
-            OptionArgParser::ToBoolean(option_arg, false, &success);
-        if (!success)
+        bool value = OptionArgParser::ToBoolean(option_arg, false, &success);
+        if (!success) {
           error.SetErrorStringWithFormat(
               "invalid boolean value for option '%c': %s", short_option,
               option_arg.data());
+          break;
+        }
+        if (short_option == 'e')
+          m_extended_backtrace = value;
+        else
+          m_filtered_backtrace = value;
       } break;
       default:
         llvm_unreachable("Unimplemented option");
@@ -99,6 +105,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
       m_count = UINT32_MAX;
       m_start = 0;
       m_extended_backtrace = false;
+      m_filtered_backtrace = true;
     }
 
     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
@@ -109,6 +116,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
     uint32_t m_count;
     uint32_t m_start;
     bool m_extended_backtrace;
+    bool m_filtered_backtrace;
   };
 
   CommandObjectThreadBacktrace(CommandInterpreter &interpreter)
@@ -199,7 +207,8 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
           strm.PutChar('\n');
           if (ext_thread_sp->GetStatus(strm, m_options.m_start,
                                        m_options.m_count,
-                                       num_frames_with_source, stop_format)) {
+                                       num_frames_with_source, stop_format,
+                                       m_options.m_filtered_backtrace)) {
             DoExtendedBacktrace(ext_thread_sp.get(), result);
           }
         }
@@ -228,7 +237,8 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
     const uint32_t num_frames_with_source = 0;
     const bool stop_format = true;
     if (!thread->GetStatus(strm, m_options.m_start, m_options.m_count,
-                           num_frames_with_source, stop_format, only_stacks)) {
+                           num_frames_with_source, stop_format,
+                           m_options.m_filtered_backtrace, only_stacks)) {
       result.AppendErrorWithFormat(
           "error displaying backtrace for thread: \"0x%4.4x\"\n",
           thread->GetIndexID());
@@ -1392,7 +1402,8 @@ class CommandObjectThreadException : public CommandObjectIterateOverThreads {
       const uint32_t num_frames_with_source = 0;
       const bool stop_format = false;
       exception_thread_sp->GetStatus(strm, 0, UINT32_MAX,
-                                     num_frames_with_source, stop_format);
+                                     num_frames_with_source, stop_format,
+                                     /*filtered*/ false);
     }
 
     return true;
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index f050cd2ebb5ae0..a59c3c18918115 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1048,6 +1048,9 @@ let Command = "thread backtrace" in {
   Arg<"FrameIndex">, Desc<"Frame in which to start the backtrace">;
   def thread_backtrace_extended : Option<"extended", "e">, Group<1>,
   Arg<"Boolean">, Desc<"Show the extended backtrace, if available">;
+  def thread_backtrace_full : Option<"filtered", "f">, Group<1>,
+  Arg<"Boolean">,
+  Desc<"Filter out frames according to installed frame recognizers">;
 }
 
 let Command = "thread step scope" in {
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 309e01e456580c..d9f1fb882fb105 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -1869,7 +1869,8 @@ void Debugger::HandleThreadEvent(const EventSP &event_sp) {
     ThreadSP thread_sp(
         Thread::ThreadEventData::GetThreadFromEvent(event_sp.get()));
     if (thread_sp) {
-      thread_sp->GetStatus(*GetAsyncOutputStream(), 0, 1, 1, stop_format);
+      thread_sp->GetStatus(*GetAsyncOutputStream(), 0, 1, 1, stop_format,
+                           /*should_filter*/ false);
     }
   }
 }
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
index c7202a47d0157e..209b971b2f2ade 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
@@ -26,6 +26,7 @@
 #include "lldb/Target/RegisterContext.h"
 #include "lldb/Target/SectionLoadList.h"
 #include "lldb/Target/StackFrame.h"
+#include "lldb/Target/StackFrameRecognizer.h"
 #include "lldb/Target/ThreadPlanRunToAddress.h"
 #include "lldb/Target/ThreadPlanStepInRange.h"
 #include "lldb/Utility/Timer.h"
@@ -40,8 +41,52 @@ static ConstString g_coro_frame = ConstString("__coro_frame");
 
 char CPPLanguageRuntime::ID = 0;
 
+/// A frame recognizer that is isntalled to hide libc++ implementation
+/// details from the backtrace.
+class LibCXXFrameRecognizer : public StackFrameRecognizer {
+  RegularExpression m_hidden_function_regex;
+  RecognizedStackFrameSP m_hidden_frame;
+
+  struct LibCXXHiddenFrame : public RecognizedStackFrame {
+    bool ShouldHide() override { return true; }
+  };
+
+public:
+  LibCXXFrameRecognizer()
+      : m_hidden_function_regex(
+            R"(^std::__1::(__function.*::operator\(\)|__invoke))"
+            R"((\[.*\])?)"    // ABI tag.
+            R"(( const)?$)"), // const.
+        m_hidden_frame(new LibCXXHiddenFrame()) {}
+
+  std::string GetName() override {
+    return "libc++ frame recognizer";
+  }
+
+  lldb::RecognizedStackFrameSP
+  RecognizeFrame(lldb::StackFrameSP frame_sp) override {
+    if (!frame_sp)
+      return {};
+    auto &sc =
+        frame_sp->GetSymbolContext(lldb::eSymbolContextFunction);
+    if (!sc.function)
+      return {};
+
+    if (m_hidden_function_regex.Execute(sc.function->GetNameNoArguments()))
+      return m_hidden_frame;
+
+    return {};
+  }
+};
+
 CPPLanguageRuntime::CPPLanguageRuntime(Process *process)
-    : LanguageRuntime(process) {}
+    : LanguageRuntime(process) {
+  if (process)
+        process->GetTarget().GetFrameRecognizerManager().AddRecognizer(
+            StackFrameRecognizerSP(new LibCXXFrameRecognizer()), {},
+            std::make_shared<RegularExpression>("^std::__1::"),
+            /*first_instruction_only*/ false);
+}
 
 bool CPPLanguageRuntime::IsAllowedRuntimeValue(ConstString name) {
   return name == g_this || name == g_promise || name == g_coro_frame;
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index e3c4f2ee398cc4..d7efa8267436fd 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -5545,7 +5545,8 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
       // Print a backtrace into the log so we can figure out where we are:
       StreamString s;
       s.PutCString("Thread state after unsuccessful completion: \n");
-      thread->GetStackFrameStatus(s, 0, UINT32_MAX, true, UINT32_MAX);
+      thread->GetStackFrameStatus(s, 0, UINT32_MAX, true, UINT32_MAX,
+                                  /*should_filter*/ false);
       log->PutString(s.GetString());
     }
     // Restore the thread state if we are going to discard the plan execution.
@@ -5819,8 +5820,8 @@ size_t Process::GetThreadStatus(Stream &strm,
           continue;
       }
       thread_sp->GetStatus(strm, start_frame, num_frames,
-                           num_frames_with_source,
-                           stop_format);
+                           num_frames_with_source, stop_format,
+                           /*should_filter*/ num_frames > 1);
       ++num_thread_infos_dumped;
     } else {
       Log *log = GetLog(LLDBLog::Process);
diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp
index 0cf9ce1bf043f5..2f284d32ce77ac 100644
--- a/lldb/source/Target/StackFrameList.cpp
+++ b/lldb/source/Target/StackFrameList.cpp
@@ -924,7 +924,7 @@ StackFrameList::GetStackFrameSPForStackFramePtr(StackFrame *stack_frame_ptr) {
 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_unique, bool should_filter,
                                  const char *selected_frame_marker) {
   size_t num_frames_displayed = 0;
 
@@ -950,8 +950,8 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
     buffer.insert(buffer.begin(), len, ' ');
     unselected_marker = buffer.c_str();
   }
+  bool filtered = false;
   const char *marker = nullptr;
-
   for (frame_idx = first_frame; frame_idx < last_frame; ++frame_idx) {
     frame_sp = GetFrameAtIndex(frame_idx);
     if (!frame_sp)
@@ -963,6 +963,15 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
       else
         marker = unselected_marker;
     }
+
+    // Hide uninteresting frames unless it's the selected frame.
+    if (should_filter && frame_sp != selected_frame_sp)
+      if (auto recognized_frame_sp = frame_sp->GetRecognizedFrame())
+        if (recognized_frame_sp->ShouldHide()) {
+          filtered = true;
+          continue;
+        }
+
     // Check for interruption here.  If we're fetching arguments, this loop
     // can go slowly:
     Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger();
@@ -979,6 +988,8 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
     ++num_frames_displayed;
   }
 
+  if (filtered)
+    strm << "Note: Some frames were hidden by frame recognizers\n";
   strm.IndentLess();
   return num_frames_displayed;
 }
diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp
index 74d1a268c6dffb..bedb89c72dd433 100644
--- a/lldb/source/Target/Thread.cpp
+++ b/lldb/source/Target/Thread.cpp
@@ -1748,7 +1748,8 @@ std::string Thread::RunModeAsString(lldb::RunMode mode) {
 
 size_t Thread::GetStatus(Stream &strm, uint32_t start_frame,
                          uint32_t num_frames, uint32_t num_frames_with_source,
-                         bool stop_format, bool only_stacks) {
+                         bool stop_format, bool should_filter,
+                         bool only_stacks) {
 
   if (!only_stacks) {
     ExecutionContext exe_ctx(shared_from_this());
@@ -1795,7 +1796,7 @@ size_t Thread::GetStatus(Stream &strm, uint32_t start_frame,
 
     num_frames_shown = GetStackFrameList()->GetStatus(
         strm, start_frame, num_frames, show_frame_info, num_frames_with_source,
-        show_frame_unique, selected_frame_marker);
+        show_frame_unique, should_filter, selected_frame_marker);
     if (num_frames == 1)
       strm.IndentLess();
     strm.IndentLess();
@@ -1893,9 +1894,11 @@ bool Thread::GetDescription(Stream &strm, lldb::DescriptionLevel level,
 
 size_t Thread::GetStackFrameStatus(Stream &strm, uint32_t first_frame,
                                    uint32_t num_frames, bool show...
[truncated]

Copy link

github-actions bot commented Aug 15, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link

github-actions bot commented Aug 15, 2024

✅ With the latest revision this PR passed the Python code formatter.

Copy link
Collaborator

@jasonmolenda jasonmolenda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation is fine, but I have some questions about this. First, SBFrame should have IsImplementationFrame or whatever terminology we use for the user view of this, like we have IsInlined and IsArtificial today so a UI can choose to omit these stack frames too.

public:
LibCXXFrameRecognizer()
: m_hidden_function_regex(
R"(^std::__1::(__function.*::operator\(\)|__invoke))"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be too ugly, but sometimes when I have a regex table, I like to include an example of a string each one is meant to match against. This first one I can get, but the next two I'm not sure what names we're matching. This mostly reflects me not paying attention to libc++ internal method names as I skip past them in backtraces, not necessarily important.

@@ -979,6 +988,8 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
++num_frames_displayed;
}

if (filtered)
strm << "Note: Some frames were hidden by frame recognizers\n";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we exposing the term "frame recognizers" to users intentionally? Could we express this as "Language implementation stack frames hidden"? I'm not convinced this is better, just asking, a lot of our errors and warning messages use lldb-internal terminology and I don't think it's helpful.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a frame recognizer multiword command so I think it's fine to expose it to the user.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is intentional. Frame recognizers are visible to users and can (in theory) be extended via scripting:

(lldb) help frame recognizer
Commands for editing and viewing frame recognizers.

Syntax: frame recognizer [<sub-command-options>] 

The following subcommands are supported:

      add    -- Add a new frame recognizer.
      clear  -- Delete all frame recognizers.
      delete -- Delete an existing frame recognizer by id.
      info   -- Show which frame recognizer is applied a stack frame (if any).
      list   -- Show a list of active frame recognizers.

For more help on any particular subcommand, type 'help <command> <subcommand>'.

lldb/source/Commands/Options.td Show resolved Hide resolved
@jasonmolenda
Copy link
Collaborator

Ah sorry was still writing and posted by accident.

For stepping we have a user exposed setting target.process.thread.step-avoid-regexp which lldb uses to step over C++ inlined methods in your code, and allow the user to extend this to add it for their own libraries. I know these frame recognizers are coming from the Language plugins so it's not as simple as a single master regex list like target.process.thread.step-avoid-regexp that a user can add on to. But i do wonder if there should be a way for a user to provide regexes that should behave this way too.

I don't know about printing the "Some frames were omitted" message by default when this takes place. Then again, I guess with the recognizers we're talking about here with C++ it will not be common, and so the one backtrace during your session where there's std::function stuff on the stack and it's omitted, it wouldn't be noisy. I don't know how frequent you think these recognizers might match for Swift code.

Copy link
Member

@medismailben medismailben left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a pretty cool idea but can we make sure it works using python frame recognizers. I believe the user should also have the ability to hide certain frames if they want, like it doesn't have to be system or language runtime frames.

lldb/include/lldb/Target/StackFrameList.h Outdated Show resolved Hide resolved
@@ -1048,6 +1048,9 @@ let Command = "thread backtrace" in {
Arg<"FrameIndex">, Desc<"Frame in which to start the backtrace">;
def thread_backtrace_extended : Option<"extended", "e">, Group<1>,
Arg<"Boolean">, Desc<"Show the extended backtrace, if available">;
def thread_backtrace_full : Option<"filtered", "f">, Group<1>,
Arg<"Boolean">,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Arg<"Boolean">,

If you make this a Boolean you have to pass an argument to the option (thread backtrace -f true). If you don't specify it, you don't have to parse the argument into a boolean SetOptionValue, you just have to check the short option and set your flag.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that this is True by default. I guess I could negate the option? What's a good name -u, --unfiltered?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: -a, --all-frames (like the option for ls) and in the help for that option we could mention that it shows all the frames including the ones hidden by a recognizer.

@walter-erquinigo
Copy link
Member

This seems pretty cool!
Question: what would happen if the top frame (the one with the PC) gets filtered out by the recognizer?

@adrian-prantl
Copy link
Collaborator Author

... But i do wonder if there should be a way for a user to provide regexes that should behave this way too.

The idea is that users can write their own frame recognizers in Python (though I have not exposed this in this patch). I'll see if I can ad this!

I don't know about printing the "Some frames were omitted" message by default when this takes place. Then again, I guess with the recognizers we're talking about here with C++ it will not be common, and so the one backtrace during your session where there's std::function stuff on the stack and it's omitted, it wouldn't be noisy. I don't know how frequent you think these recognizers might match for Swift code.

Thinking of it, it is probably good enough to explain this in help thread backtrace and help bt?

@adrian-prantl
Copy link
Collaborator Author

This seems pretty cool! Question: what would happen if the top frame (the one with the PC) gets filtered out by the recognizer?

The current frame never gets filtered. If you select a filtered frame with f 123 and run bt the current frame will be there.

@adrian-prantl
Copy link
Collaborator Author

Action items so far:

  • change the option to --unfiltered (no argument)
  • expose in SBFrame
  • expose in Python

@vogelsgesang
Copy link
Member

Should hidden frames also be skipped by StepOut?

@jimingham
Copy link
Collaborator

When we were talking about this originally I thought that StackFrames would get an "ImplementationFrame" property, and we would use that to determine whether to hide the frame in bt (when not passing --unfiltered). Then the frame recognizers when they are run on the frame in the ordinary course of things (we do this to determine recognized args anyway) they would set the property on the StackFrame. Then StackFrameList could just check that property.
RecognizeFrame might be expensive, and we don't currently have any machinery in place to make sure it caches the results per stop. Most of the RecognizeFrame's just do the work every time.
I also think for structural reasons we shouldn't force FrameRecognizers to be the only entity that can mark frames as "should be hidden."

@jimingham
Copy link
Collaborator

BTW, we should definitely get StepOut to step out past hidden frames. That should be pretty easy to do.

@adrian-prantl
Copy link
Collaborator Author

When we were talking about this originally I thought that StackFrames would get an "ImplementationFrame" property, and we would use that to determine whether to hide the frame in bt (when not passing --unfiltered). Then the frame recognizers when they are run on the frame in the ordinary course of things (we do this to determine recognized args anyway) they would set the property on the StackFrame. Then StackFrameList could just check that property. RecognizeFrame might be expensive, and we don't currently have any machinery in place to make sure it caches the results per stop. Most of the RecognizeFrame's just do the work every time. I also think for structural reasons we shouldn't force FrameRecognizers to be the only entity that can mark frames as "should be hidden."

I understand that not recomputing them might be desirable, but this is not how recognizers currently work. The only place frame recognizers currently get called is by Thread::GetStopDescription() (for frame #0) and by Thread::GetCurrentException() (also frame #0). So it's not true that we are computing them anyway and thus have an opportunity to precompute the isHidden attribute. I also think that from an architectural perspective it would be wrong for a frame recognizer to be allowed to modify the state of a StackFrame.

When a frame recognizer is registered it takes a module regex a function regex and a first_instruction flag. All three of these must match before the recognizer is executed. That's num_recognizers*num_frames regexes. That's not expensive in the grans scheme of things, but you're right that it's not necessary.

I'll see if I can hide the recognizer invocation inside StackFrame::shouldHide() and let StackFrame cache the result instead of having the Recognizer modify the StackFrame. Maybe I can come up with some cache invalidation strategy that makes sense.

@adrian-prantl adrian-prantl force-pushed the 126629381 branch 2 times, most recently from 6653321 to 8f6af48 Compare August 20, 2024 03:11
@adrian-prantl
Copy link
Collaborator Author

I uploaded a new version of the patch that addresses all outstanding feedback, except for finish (I haven't looked at how that is implemented yet):

  • correctly caching recognized frames
  • hiding the recognizer API behind Frame::IsHidden()
  • exposing it via the SBAPI
  • extending it via Python
  • renaming the filtering option

@adrian-prantl
Copy link
Collaborator Author

@medismailben I copied&pasted the python interface code from the function above; could you review that code extra carefully?

Copy link
Member

@medismailben medismailben left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM modulo comment.

@adrian-prantl
Copy link
Collaborator Author

Implemented StepOut.

lldb/include/lldb/Target/StackFrameRecognizer.h Outdated Show resolved Hide resolved
lldb/source/Commands/CommandObjectFrame.cpp Outdated Show resolved Hide resolved
: LanguageRuntime(process) {
if (process)
process->GetTarget().GetFrameRecognizerManager().AddRecognizer(
StackFrameRecognizerSP(new LibCXXFrameRecognizer()), {},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: make_shared?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same problem, it's a subclass.

cjdb pushed a commit to cjdb/llvm-project that referenced this pull request Aug 23, 2024
llvm#104523)"

This reverts commit f01f80c.

This commit introduces an msan violation. See the discussion on llvm#104523.
adrian-prantl added a commit that referenced this pull request Aug 23, 2024
@adrian-prantl
Copy link
Collaborator Author

I reverted the reverts and pushed two commits to fix the missing initialization.

adrian-prantl added a commit to adrian-prantl/llvm-project that referenced this pull request Aug 23, 2024
…104523)

Compilers and language runtimes often use helper functions that are
fundamentally uninteresting when debugging anything but the
compiler/runtime itself. This patch introduces a user-extensible
mechanism that allows for these frames to be hidden from backtraces and
automatically skipped over when navigating the stack with `up` and
`down`.

This does not affect the numbering of frames, so `f <N>` will still
provide access to the hidden frames. The `bt` output will also print a
hint that frames have been hidden.

My primary motivation for this feature is to hide thunks in the Swift
programming language, but I'm including an example recognizer for
`std::function::operator()` that I wished for myself many times while
debugging LLDB.

rdar://126629381

Example output. (Yes, my proof-of-concept recognizer could hide even
more frames if we had a method that returned the function name without
the return type or I used something that isn't based off regex, but it's
really only meant as an example).

before:
```
(lldb) thread backtrace --filtered=false
* thread swiftlang#1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame swiftlang#1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame swiftlang#2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame swiftlang#3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()[abi:se200000](this=0x000000016fdff280, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:171:12
    frame swiftlang#4: 0x00000001000026bc a.out`std::__1::__function::__func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()(this=0x000000016fdff278, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:313:10
    frame swiftlang#5: 0x0000000100003c38 a.out`std::__1::__function::__value_func<int (int, int)>::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12
    frame swiftlang#6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame swiftlang#7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame swiftlang#8: 0x0000000183cdf154 dyld`start + 2476
(lldb)
```

after

```
(lldb) bt
* thread swiftlang#1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame swiftlang#1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame swiftlang#2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame swiftlang#6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame swiftlang#7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame swiftlang#8: 0x0000000183cdf154 dyld`start + 2476
Note: Some frames were hidden by frame recognizers
```

(cherry picked from commit f01f80c)
adrian-prantl pushed a commit to adrian-prantl/llvm-project that referenced this pull request Aug 23, 2024
The issue was introduced in
llvm#104523.

The code introduces the `ret_val` variable but does not use it. Instead
it returns a pointer, which gets implicitly converted to bool.

(cherry picked from commit 6528157)
5chmidti pushed a commit that referenced this pull request Aug 24, 2024
@petrhosek
Copy link
Member

I reverted the reverts and pushed two commits to fix the missing initialization.

The libc++ issue I described earlier still hasn't been addressed so I'm going to revert the changes again...

petrhosek added a commit that referenced this pull request Aug 26, 2024
This is a fix forward for the issue introduced in #104523.
@petrhosek
Copy link
Member

I attempted a fix forward in 68a1593 but the test is still failing, although with a slightly different error now:

======================================================================
FAIL: test_backtrace (TestStdFunctionRecognizer.LibCxxStdFunctionRecognizerTestCase.test_backtrace)
   Test that std::function implementation details are hidden in bt
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/b/s/w/ir/x/w/llvm-llvm-project/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py", line 23, in test_backtrace
    self.expect(
  File "/b/s/w/ir/x/w/llvm-llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 2464, in expect
    self.fail(log_msg)
AssertionError: Ran command:
"thread backtrace"

Got output:
* thread #1, name = 'a.out', stop reason = breakpoint 1.1
  * frame #0: 0x000056243befb40a a.out`foo(x=1, y=1) at main.cpp:4:10
    frame #1: 0x000056243befb819 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__2::__invoke[abi:ne200000]<int (*&)(int, int), int, int>(__f=0x00007fff84d3db50, __args=0x00007fff84d3dac4, __args=0x00007fff84d3dac0) at invoke.h:149:25
    frame #2: 0x000056243befb7e5 a.out`int std::__2::__invoke_void_return_wrapper<int, false>::__call[abi:ne200000]<int (*&)(int, int), int, int>(__args=0x00007fff84d3db50, __args=0x00007fff84d3dac4, __args=0x00007fff84d3dac0) at invoke.h:216:12
    frame #4: 0x000056243befb75b a.out`int std::__2::__function::__policy_invoker<int (int, int)>::__call_impl[abi:ne200000]<std::__2::__function::__default_alloc_func<int (*)(int, int), int (int, int)>>(__buf=0x00007fff84d3db50, __args=1, __args=1) at function.h:610:12
    frame #6: 0x000056243befb4f3 a.out`std::__2::function<int (int, int)>::operator()(this=0x00007fff84d3db50, __arg=1, __arg=1) const at function.h:989:10
    frame #7: 0x000056243befb45b a.out`main(argc=1, argv=0x00007fff84d3dc78) at main.cpp:9:10
    frame #8: 0x00007fc200d2ed0a libc.so.6`__libc_start_main + 234
    frame #9: 0x000056243befb36a a.out`_start + 42

Not expecting regex pattern: "frame.*std::__.*::__function" (was found, matched "frame #4: 0x000056243befb75b a.out`int std::__2::__function::__policy_invoker<int (int, int)>::__call_impl[abi:ne200000]<std::__2::__function")
Config=x86_64-/b/s/w/ir/x/w/llvm_build/bin/clang
----------------------------------------------------------------------

@vogelsgesang
Copy link
Member

Would be interesting if this is fixed by #105695. How can I run the test cases against libc++'s unstable ABI locally?

@petrhosek
Copy link
Member

petrhosek commented Aug 26, 2024

Looks like libc++ uses a different layout for std::function with ABI v1 and v2, see

# ifndef _LIBCPP_ABI_OPTIMIZED_FUNCTION
typedef __function::__value_func<_Rp(_ArgTypes...)> __func;
# else
typedef __function::__policy_func<_Rp(_ArgTypes...)> __func;
# endif

@petrhosek
Copy link
Member

Would be interesting if this is fixed by #105695. How can I run the test cases against libc++'s unstable ABI locally?

Set -D LIBCXX_ABI_VERSION=2 in your CMake invocation.

@vogelsgesang
Copy link
Member

I updated #105695 and locally verified that it now fixes the test case for libc++ ABI v2

@jimingham
Copy link
Collaborator

When we were talking about this originally I thought that StackFrames would get an "ImplementationFrame" property, and we would use that to determine whether to hide the frame in bt (when not passing --unfiltered). Then the frame recognizers when they are run on the frame in the ordinary course of things (we do this to determine recognized args anyway) they would set the property on the StackFrame. Then StackFrameList could just check that property. RecognizeFrame might be expensive, and we don't currently have any machinery in place to make sure it caches the results per stop. Most of the RecognizeFrame's just do the work every time. I also think for structural reasons we shouldn't force FrameRecognizers to be the only entity that can mark frames as "should be hidden."

I understand that not recomputing them might be desirable, but this is not how recognizers currently work. The only place frame recognizers currently get called is by Thread::GetStopDescription() (for frame #0) and by Thread::GetCurrentException() (also frame #0). So it's not true that we are computing them anyway and thus have an opportunity to precompute the isHidden attribute. I also think that from an architectural perspective it would be wrong for a frame recognizer to be allowed to modify the state of a StackFrame.

This is entirely tangential to this patch, which looks fine to me, but one of the jobs of a Frame recognizer is to add arguments to a StackFrame that doesn't have them. That's way more intrusive than just setting a "ShouldHide" type flag...

When a frame recognizer is registered it takes a module regex a function regex and a first_instruction flag. All three of these must match before the recognizer is executed. That's num_recognizers*num_frames regexes. That's not expensive in the grans scheme of things, but you're right that it's not necessary.

I'll see if I can hide the recognizer invocation inside StackFrame::shouldHide() and let StackFrame cache the result instead of having the Recognizer modify the StackFrame. Maybe I can come up with some cache invalidation strategy that makes sense.

dmpolukhin pushed a commit to dmpolukhin/llvm-project that referenced this pull request Sep 2, 2024
…104523)

Compilers and language runtimes often use helper functions that are
fundamentally uninteresting when debugging anything but the
compiler/runtime itself. This patch introduces a user-extensible
mechanism that allows for these frames to be hidden from backtraces and
automatically skipped over when navigating the stack with `up` and
`down`.

This does not affect the numbering of frames, so `f <N>` will still
provide access to the hidden frames. The `bt` output will also print a
hint that frames have been hidden.

My primary motivation for this feature is to hide thunks in the Swift
programming language, but I'm including an example recognizer for
`std::function::operator()` that I wished for myself many times while
debugging LLDB.

rdar://126629381


Example output. (Yes, my proof-of-concept recognizer could hide even
more frames if we had a method that returned the function name without
the return type or I used something that isn't based off regex, but it's
really only meant as an example).

before:
```
(lldb) thread backtrace --filtered=false
* thread llvm#1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame llvm#1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame llvm#2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame llvm#3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()[abi:se200000](this=0x000000016fdff280, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:171:12
    frame llvm#4: 0x00000001000026bc a.out`std::__1::__function::__func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()(this=0x000000016fdff278, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:313:10
    frame llvm#5: 0x0000000100003c38 a.out`std::__1::__function::__value_func<int (int, int)>::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12
    frame llvm#6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame llvm#7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame llvm#8: 0x0000000183cdf154 dyld`start + 2476
(lldb) 
```

after

```
(lldb) bt
* thread llvm#1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame llvm#1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame llvm#2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame llvm#6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame llvm#7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame llvm#8: 0x0000000183cdf154 dyld`start + 2476
Note: Some frames were hidden by frame recognizers
```
dmpolukhin pushed a commit to dmpolukhin/llvm-project that referenced this pull request Sep 2, 2024
The issue was introduced in
llvm#104523.

The code introduces the `ret_val` variable but does not use it. Instead
it returns a pointer, which gets implicitly converted to bool.
dmpolukhin pushed a commit to dmpolukhin/llvm-project that referenced this pull request Sep 2, 2024
This reverts commit 6528157.

I'm reverting llvm#104523
(llvm@f01f80c)
and this fixup belongs to the same series of changes.
dmpolukhin pushed a commit to dmpolukhin/llvm-project that referenced this pull request Sep 2, 2024
This reverts commit 6f45602, which
depends on llvm#104523, which I'm
reverting.
dmpolukhin pushed a commit to dmpolukhin/llvm-project that referenced this pull request Sep 2, 2024
llvm#104523)"

This reverts commit f01f80c.

This commit introduces an msan violation. See the discussion on llvm#104523.
dmpolukhin pushed a commit to dmpolukhin/llvm-project that referenced this pull request Sep 2, 2024
dmpolukhin pushed a commit to dmpolukhin/llvm-project that referenced this pull request Sep 2, 2024
This is a fix forward for the issue introduced in llvm#104523.
adrian-prantl added a commit to adrian-prantl/llvm-project that referenced this pull request Sep 17, 2024
…104523)

Compilers and language runtimes often use helper functions that are
fundamentally uninteresting when debugging anything but the
compiler/runtime itself. This patch introduces a user-extensible
mechanism that allows for these frames to be hidden from backtraces and
automatically skipped over when navigating the stack with `up` and
`down`.

This does not affect the numbering of frames, so `f <N>` will still
provide access to the hidden frames. The `bt` output will also print a
hint that frames have been hidden.

My primary motivation for this feature is to hide thunks in the Swift
programming language, but I'm including an example recognizer for
`std::function::operator()` that I wished for myself many times while
debugging LLDB.

rdar://126629381

Example output. (Yes, my proof-of-concept recognizer could hide even
more frames if we had a method that returned the function name without
the return type or I used something that isn't based off regex, but it's
really only meant as an example).

before:
```
(lldb) thread backtrace --filtered=false
* thread swiftlang#1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame swiftlang#1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame swiftlang#2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame swiftlang#3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()[abi:se200000](this=0x000000016fdff280, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:171:12
    frame swiftlang#4: 0x00000001000026bc a.out`std::__1::__function::__func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()(this=0x000000016fdff278, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:313:10
    frame swiftlang#5: 0x0000000100003c38 a.out`std::__1::__function::__value_func<int (int, int)>::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12
    frame swiftlang#6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame swiftlang#7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame swiftlang#8: 0x0000000183cdf154 dyld`start + 2476
(lldb)
```

after

```
(lldb) bt
* thread swiftlang#1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame swiftlang#1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame swiftlang#2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame swiftlang#6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame swiftlang#7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame swiftlang#8: 0x0000000183cdf154 dyld`start + 2476
Note: Some frames were hidden by frame recognizers
```

(cherry picked from commit f01f80c)
adrian-prantl pushed a commit to adrian-prantl/llvm-project that referenced this pull request Sep 17, 2024
The issue was introduced in
llvm#104523.

The code introduces the `ret_val` variable but does not use it. Instead
it returns a pointer, which gets implicitly converted to bool.

(cherry picked from commit 6528157)
adrian-prantl added a commit to adrian-prantl/llvm-project that referenced this pull request Sep 19, 2024
…104523)

Compilers and language runtimes often use helper functions that are
fundamentally uninteresting when debugging anything but the
compiler/runtime itself. This patch introduces a user-extensible
mechanism that allows for these frames to be hidden from backtraces and
automatically skipped over when navigating the stack with `up` and
`down`.

This does not affect the numbering of frames, so `f <N>` will still
provide access to the hidden frames. The `bt` output will also print a
hint that frames have been hidden.

My primary motivation for this feature is to hide thunks in the Swift
programming language, but I'm including an example recognizer for
`std::function::operator()` that I wished for myself many times while
debugging LLDB.

rdar://126629381

Example output. (Yes, my proof-of-concept recognizer could hide even
more frames if we had a method that returned the function name without
the return type or I used something that isn't based off regex, but it's
really only meant as an example).

before:
```
(lldb) thread backtrace --filtered=false
* thread swiftlang#1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame swiftlang#1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame swiftlang#2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame swiftlang#3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()[abi:se200000](this=0x000000016fdff280, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:171:12
    frame swiftlang#4: 0x00000001000026bc a.out`std::__1::__function::__func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()(this=0x000000016fdff278, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:313:10
    frame swiftlang#5: 0x0000000100003c38 a.out`std::__1::__function::__value_func<int (int, int)>::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12
    frame swiftlang#6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame swiftlang#7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame swiftlang#8: 0x0000000183cdf154 dyld`start + 2476
(lldb)
```

after

```
(lldb) bt
* thread swiftlang#1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame swiftlang#1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame swiftlang#2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame swiftlang#6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame swiftlang#7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame swiftlang#8: 0x0000000183cdf154 dyld`start + 2476
Note: Some frames were hidden by frame recognizers
```

(cherry picked from commit f01f80c)
adrian-prantl pushed a commit to adrian-prantl/llvm-project that referenced this pull request Sep 19, 2024
The issue was introduced in
llvm#104523.

The code introduces the `ret_val` variable but does not use it. Instead
it returns a pointer, which gets implicitly converted to bool.

(cherry picked from commit 6528157)
Michael137 pushed a commit to swiftlang/llvm-project that referenced this pull request Sep 20, 2024
…104523)

Compilers and language runtimes often use helper functions that are
fundamentally uninteresting when debugging anything but the
compiler/runtime itself. This patch introduces a user-extensible
mechanism that allows for these frames to be hidden from backtraces and
automatically skipped over when navigating the stack with `up` and
`down`.

This does not affect the numbering of frames, so `f <N>` will still
provide access to the hidden frames. The `bt` output will also print a
hint that frames have been hidden.

My primary motivation for this feature is to hide thunks in the Swift
programming language, but I'm including an example recognizer for
`std::function::operator()` that I wished for myself many times while
debugging LLDB.

rdar://126629381

Example output. (Yes, my proof-of-concept recognizer could hide even
more frames if we had a method that returned the function name without
the return type or I used something that isn't based off regex, but it's
really only meant as an example).

before:
```
(lldb) thread backtrace --filtered=false
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame #1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame #3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()[abi:se200000](this=0x000000016fdff280, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:171:12
    frame #4: 0x00000001000026bc a.out`std::__1::__function::__func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()(this=0x000000016fdff278, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:313:10
    frame #5: 0x0000000100003c38 a.out`std::__1::__function::__value_func<int (int, int)>::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12
    frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame #8: 0x0000000183cdf154 dyld`start + 2476
(lldb)
```

after

```
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame #1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame #8: 0x0000000183cdf154 dyld`start + 2476
Note: Some frames were hidden by frame recognizers
```

(cherry picked from commit f01f80c)
Michael137 pushed a commit to swiftlang/llvm-project that referenced this pull request Sep 20, 2024
The issue was introduced in
llvm#104523.

The code introduces the `ret_val` variable but does not use it. Instead
it returns a pointer, which gets implicitly converted to bool.

(cherry picked from commit 6528157)
JDevlieghere pushed a commit to swiftlang/llvm-project that referenced this pull request Oct 16, 2024
This reverts commit 6f45602, which
depends on llvm#104523, which I'm
reverting.

(cherry picked from commit aa70f83)
felipepiovezan pushed a commit to felipepiovezan/llvm-project that referenced this pull request Oct 30, 2024
This is a fix forward for the issue introduced in llvm#104523.

(cherry picked from commit 68a1593)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.