[Lldb-commits] [lldb] [lldb] Reject thread until targets outside of an inline frame's function (PR #227800)

Felipe de Azevedo Piovezan via lldb-commits lldb-commits at lists.llvm.org
Thu Oct 1 01:23:46 PDT 2026


https://github.com/felipepiovezan updated https://github.com/llvm/llvm-project/pull/227800

>From 4b21e4d8ed1b47fb265eb9ff813a0248861107ec Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Mon, 28 Sep 2026 09:55:35 +0100
Subject: [PATCH 1/2] [lldb] Reject thread until targets outside of an inline
 frame's function

`thread until -f N <line>` rejects a line outside of the function of
frame N.

Before this patch, for an inline frame, it checked the concrete
function, not the inlined function, so it accepted lines of other
inlined functions. The until plan then ignored a hit of such a target,
because it is not in the scope of frame N.

This commit changes GetStepUntilAddresses to only accepts an address if
its innermost inlined function is the one of frame N, as it does for a
regular frame.

As a before/after example, consider this backtrace:

```
  frame #0: 0x00000001000003c0 deep`sink(x=81) at deep.c:4:6 [opt]
  frame #1: 0x0000000100000418 deep`level3(a=48) at deep.c:11:3 [opt] [inlined]
  frame #2: 0x0000000100000404 deep`level2(b=37) at deep.c:20:3 [opt] [inlined]
  frame #3: 0x00000001000003f0 deep`level1(c=42) at deep.c:28:3 [opt] [inlined]
  frame #4: 0x00000001000003dc deep`main at deep.c:33:3 [opt]
```

This patch changes behavior as follows:

```
command                             gdb        lldb before    lldb after
until -f 1 <later line in frame 2>  that line  process exits  error
until -f 2 <later line in frame 1>  that line  process exits  error
---
 lldb/include/lldb/Target/StackFrame.h         |  6 ++
 lldb/include/lldb/Target/ThreadPlan.h         |  2 +-
 lldb/source/Target/StackFrame.cpp             | 15 +++++
 lldb/source/Target/ThreadPlan.cpp             |  5 +-
 .../functionalities/inline-frame-pc/Makefile  |  3 +
 .../inline-frame-pc/TestInlineFrameUntil.py   | 57 +++++++++++++++++++
 .../functionalities/inline-frame-pc/main.c    | 30 ++++++++++
 7 files changed, 113 insertions(+), 5 deletions(-)
 create mode 100644 lldb/test/API/functionalities/inline-frame-pc/Makefile
 create mode 100644 lldb/test/API/functionalities/inline-frame-pc/TestInlineFrameUntil.py
 create mode 100644 lldb/test/API/functionalities/inline-frame-pc/main.c

diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h
index e6ac3d1ad5275..8fa0eef067f4a 100644
--- a/lldb/include/lldb/Target/StackFrame.h
+++ b/lldb/include/lldb/Target/StackFrame.h
@@ -231,6 +231,12 @@ class StackFrame : public ExecutionContextScope,
   ///   not be provided.
   virtual Block *GetFrameBlock();
 
+  /// Let F be this frame's function. Returns true if \p addr points to code
+  /// inside F.
+  /// Note: if some other function F2 has been inlined into F, and \p
+  /// addr points to code of the inlined copy of F2, this returns false.
+  bool IsAddressInFrameScope(const Address &addr);
+
   /// Get the RegisterContext for this frame, if possible.
   ///
   /// Returns a shared pointer to the RegisterContext for this stack frame.
diff --git a/lldb/include/lldb/Target/ThreadPlan.h b/lldb/include/lldb/Target/ThreadPlan.h
index 864ea79c54d6f..0a663da896e97 100644
--- a/lldb/include/lldb/Target/ThreadPlan.h
+++ b/lldb/include/lldb/Target/ThreadPlan.h
@@ -628,7 +628,7 @@ class ThreadPlanNull : public ThreadPlan {
 };
 
 /// Returns the load addresses of the line table entries for \p lines in
-/// \p file, and of \p addresses, that are in the function of \p frame. A
+/// \p file, and of \p addresses, that are in the scope of \p frame. A
 /// line without entries resolves to the nearest following line with
 /// entries. Fails if no address is left.
 llvm::Expected<std::vector<lldb::addr_t>>
diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 59eda286e487f..6e3b9cfe87da8 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -302,6 +302,21 @@ Block *StackFrame::GetFrameBlock() {
   return nullptr;
 }
 
+bool StackFrame::IsAddressInFrameScope(const Address &addr) {
+  Block *frame_block = GetFrameBlock();
+  Block *addr_block = addr.CalculateSymbolContextBlock();
+  if (!frame_block || !addr_block)
+    return false;
+  // Do they same the same concrete function?
+  if (addr_block->CalculateSymbolContextFunction() !=
+      frame_block->CalculateSymbolContextFunction())
+    return false;
+
+  // Do they represent the same inlined function?
+  return addr_block->GetContainingInlinedBlock() ==
+         frame_block->GetContainingInlinedBlock();
+}
+
 // Get the symbol context if we already haven't done so by resolving the
 // PC address as much as possible. This way when we pass around a
 // StackFrame object, everyone will have as much information as possible and no
diff --git a/lldb/source/Target/ThreadPlan.cpp b/lldb/source/Target/ThreadPlan.cpp
index 52ae6c1df6198..57a1b597975c5 100644
--- a/lldb/source/Target/ThreadPlan.cpp
+++ b/lldb/source/Target/ThreadPlan.cpp
@@ -311,10 +311,7 @@ llvm::Expected<std::vector<addr_t>> lldb_private::GetStepUntilAddresses(
   std::vector<addr_t> until_addrs;
   auto add_if_in_scope = [&](const Address &addr) {
     addr_t load_addr = addr.GetLoadAddress(target_sp.get());
-    AddressRange unused;
-    if (load_addr != LLDB_INVALID_ADDRESS &&
-        frame_sc.function->GetRangeContainingLoadAddress(load_addr, *target_sp,
-                                                         unused))
+    if (load_addr != LLDB_INVALID_ADDRESS && frame.IsAddressInFrameScope(addr))
       until_addrs.push_back(load_addr);
   };
 
diff --git a/lldb/test/API/functionalities/inline-frame-pc/Makefile b/lldb/test/API/functionalities/inline-frame-pc/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-frame-pc/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/inline-frame-pc/TestInlineFrameUntil.py b/lldb/test/API/functionalities/inline-frame-pc/TestInlineFrameUntil.py
new file mode 100644
index 0000000000000..2188614b4fcda
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-frame-pc/TestInlineFrameUntil.py
@@ -0,0 +1,57 @@
+"""
+Test thread until with inlined functions.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestInlineFrameUntil(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def run_to_sink(self):
+        self.build()
+        _, _, thread, _ = lldbutil.run_to_name_breakpoint(self, "sink")
+        return thread
+
+    def until_from_frame(self, frame_idx, marker):
+        thread = self.run_to_sink()
+        self.runCmd(f"thread until -f {frame_idx} {line_number('main.c', marker)}")
+        return thread.GetFrameAtIndex(0)
+
+    def check_stops_at(self, frame_idx, marker):
+        frame = self.until_from_frame(frame_idx, marker)
+        self.assertEqual(frame.GetLineEntry().GetLine(), line_number("main.c", marker))
+
+    def check_rejected(self, frame_idx, marker):
+        self.run_to_sink()
+        self.expect(
+            f"thread until -f {frame_idx} {line_number('main.c', marker)}",
+            error=True,
+            substrs=["Until target outside of the current function"],
+        )
+
+    def test_until_in_same_inline_frame(self):
+        self.check_stops_at(1, "// until in level3")
+
+    def test_until_in_parent_inline_frame(self):
+        self.check_stops_at(2, "// until in level2")
+
+    def test_until_in_inlining_caller(self):
+        self.check_rejected(1, "// until in level2")
+
+    def test_until_in_inlined_callee(self):
+        self.check_rejected(2, "// until in level3")
+
+    def test_step_over_until_in_inlining_caller(self):
+        thread = self.run_to_sink()
+        error = thread.StepOverUntil(
+            thread.GetFrameAtIndex(1),
+            lldb.SBFileSpec("main.c"),
+            line_number("main.c", "// until in level2"),
+        )
+        self.assertIn(
+            "Until target outside of the current function", error.GetCString()
+        )
diff --git a/lldb/test/API/functionalities/inline-frame-pc/main.c b/lldb/test/API/functionalities/inline-frame-pc/main.c
new file mode 100644
index 0000000000000..e035e623866b9
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-frame-pc/main.c
@@ -0,0 +1,30 @@
+volatile int g;
+
+__attribute__((noinline)) void sink(int x) { g = x; }
+
+static inline __attribute__((always_inline)) void level3(int a) {
+  g = a; // before sink
+  sink(a);
+  g = a + 1; // break in level3
+  g = a + 2; // until in level3
+}
+
+static inline __attribute__((always_inline)) void level2(int b) {
+  g = b;
+  level3(b + 1);
+  g = b + 1; // until in level2
+}
+
+static inline __attribute__((always_inline)) void level1(int c) {
+  g = c;
+  level2(c + 1);
+}
+
+__attribute__((noinline)) void outer(void) {
+  level1(42);
+}
+
+int main(void) {
+  outer();
+  return 0;
+}

>From fdfe74d4f6c1546eb6b880d66f8c797cd3f2002e Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Thu, 1 Oct 2026 09:23:04 +0100
Subject: [PATCH 2/2] fixup! address review feedback

---
 lldb/source/Target/StackFrame.cpp                    | 2 +-
 lldb/test/API/functionalities/inline-frame-pc/main.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 6e3b9cfe87da8..6696077582eef 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -307,7 +307,7 @@ bool StackFrame::IsAddressInFrameScope(const Address &addr) {
   Block *addr_block = addr.CalculateSymbolContextBlock();
   if (!frame_block || !addr_block)
     return false;
-  // Do they same the same concrete function?
+  // Do they represent the same concrete function?
   if (addr_block->CalculateSymbolContextFunction() !=
       frame_block->CalculateSymbolContextFunction())
     return false;
diff --git a/lldb/test/API/functionalities/inline-frame-pc/main.c b/lldb/test/API/functionalities/inline-frame-pc/main.c
index e035e623866b9..2ab720a609f13 100644
--- a/lldb/test/API/functionalities/inline-frame-pc/main.c
+++ b/lldb/test/API/functionalities/inline-frame-pc/main.c
@@ -21,7 +21,7 @@ static inline __attribute__((always_inline)) void level1(int c) {
 }
 
 __attribute__((noinline)) void outer(void) {
-  level1(42);
+  level1(42); // some comment to make clang-format happy.
 }
 
 int main(void) {



More information about the lldb-commits mailing list