[Lldb-commits] [lldb] [lldb] Add support for synthetic LineEntry objects without valid address ranges (PR #158811)
Med Ismail Bennani via lldb-commits
lldb-commits at lists.llvm.org
Thu Dec 4 11:23:38 PST 2025
https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/158811
>From 263bcbd37eca7e1c73d2dafcc7d490617daea548 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Thu, 4 Dec 2025 11:13:18 -0800
Subject: [PATCH] [lldb] Add support for synthetic LineEntry objects without
valid address ranges
Scripted Frames that materialize Python functions are PC-less by design,
meaning they don't have valid address ranges. Previously, LineEntry::IsValid()
required both a valid address range and a line number, preventing scripted
frames from creating valid line entries for these synthetic stack frames.
Relaxing this requirement is necessary since `SBSymbolContext::SetLineEntry`
will first check if the LineEntry is valid and discard it otherwise.
This change introduces an `is_synthetic` flag that gets set when LineEntry
objects are created or modified through the SBAPI (specifically via SetLine).
When this flag is set, IsValid() no longer requires a valid address range,
only a valid line number.
This is risk-free because the flag is only set for LineEntry objects created
through the SBAPI, which are primarily used by scripted processes and frames.
Regular debug information-derived line entries continue to require valid
address ranges.
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
lldb/include/lldb/Symbol/LineEntry.h | 5 +
lldb/source/API/SBLineEntry.cpp | 2 +
lldb/source/Symbol/LineEntry.cpp | 5 +-
.../dummy_scripted_process.py | 14 ++
.../python_api/sblineentry/TestSBLineEntry.py | 144 ++++++++++++++++++
5 files changed, 168 insertions(+), 2 deletions(-)
create mode 100644 lldb/test/API/python_api/sblineentry/TestSBLineEntry.py
diff --git a/lldb/include/lldb/Symbol/LineEntry.h b/lldb/include/lldb/Symbol/LineEntry.h
index a61b72f253dd7..e370d9accdd6c 100644
--- a/lldb/include/lldb/Symbol/LineEntry.h
+++ b/lldb/include/lldb/Symbol/LineEntry.h
@@ -136,6 +136,11 @@ struct LineEntry {
/// The section offset address range for this line entry.
AddressRange range;
+ /// This gets set for LineEntries created from the SBAPI, where we don't
+ /// necessary have a valid address range. When set, LineEntry::IsValid doesn't
+ /// check the `range` validity.
+ bool is_synthetic;
+
/// The source file, possibly mapped by the target.source-map setting.
SupportFileNSP file_sp;
diff --git a/lldb/source/API/SBLineEntry.cpp b/lldb/source/API/SBLineEntry.cpp
index 0f4936f32a074..1c9401d420545 100644
--- a/lldb/source/API/SBLineEntry.cpp
+++ b/lldb/source/API/SBLineEntry.cpp
@@ -132,6 +132,8 @@ void SBLineEntry::SetLine(uint32_t line) {
LLDB_INSTRUMENT_VA(this, line);
ref().line = line;
+ if (!ref().range.IsValid())
+ ref().is_synthetic = true;
}
void SBLineEntry::SetColumn(uint32_t column) {
diff --git a/lldb/source/Symbol/LineEntry.cpp b/lldb/source/Symbol/LineEntry.cpp
index c941a6927cb93..a68dbe63cf3f8 100644
--- a/lldb/source/Symbol/LineEntry.cpp
+++ b/lldb/source/Symbol/LineEntry.cpp
@@ -14,7 +14,7 @@
using namespace lldb_private;
LineEntry::LineEntry()
- : range(), file_sp(std::make_shared<SupportFile>()),
+ : range(), is_synthetic(false), file_sp(std::make_shared<SupportFile>()),
original_file_sp(std::make_shared<SupportFile>()),
is_start_of_statement(0), is_start_of_basic_block(0), is_prologue_end(0),
is_epilogue_begin(0), is_terminal_entry(0) {}
@@ -33,7 +33,8 @@ void LineEntry::Clear() {
}
bool LineEntry::IsValid() const {
- return range.GetBaseAddress().IsValid() && line != LLDB_INVALID_LINE_NUMBER;
+ return (range.GetBaseAddress().IsValid() || is_synthetic) &&
+ line != LLDB_INVALID_LINE_NUMBER;
}
bool LineEntry::DumpStopContext(Stream *s, bool show_fullpaths) const {
diff --git a/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py b/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py
index 835267221ddb4..a9459682e70a8 100644
--- a/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py
+++ b/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py
@@ -74,6 +74,20 @@ def __init__(self, process, args):
self.frames.append(DummyScriptedFrame(self, args, len(self.frames), "bar"))
self.frames.append(DummyScriptedFrame(self, args, len(self.frames), "foo"))
+ cwd = os.path.dirname(os.path.abspath(__file__))
+
+ le = lldb.SBLineEntry()
+ le.SetFileSpec(lldb.SBFileSpec(os.path.join(cwd, "baz.cpp"), True))
+ le.SetLine(9)
+ le.SetColumn(10)
+
+ sym_ctx = lldb.SBSymbolContext()
+ sym_ctx.SetLineEntry(le)
+
+ self.frames.append(
+ DummyScriptedFrame(self, args, len(self.frames), "baz", sym_ctx)
+ )
+
def get_thread_id(self) -> int:
return 0x19
diff --git a/lldb/test/API/python_api/sblineentry/TestSBLineEntry.py b/lldb/test/API/python_api/sblineentry/TestSBLineEntry.py
new file mode 100644
index 0000000000000..ee8404c6b8560
--- /dev/null
+++ b/lldb/test/API/python_api/sblineentry/TestSBLineEntry.py
@@ -0,0 +1,144 @@
+"""
+Test SBLineEntry APIs, particularly synthetic line entries.
+"""
+
+import os
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class SBLineEntryTestCase(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def test_synthetic_line_entry(self):
+ """Test that synthetic LineEntry objects (created via SBAPI) can be
+ valid without a valid address range and can be set in SBSymbolContext."""
+
+ # Test creating a synthetic line entry via SBAPI.
+ line_entry = lldb.SBLineEntry()
+ self.assertFalse(
+ line_entry.IsValid(), "Default constructed line entry should be invalid"
+ )
+
+ # Set line number - this should mark the line entry as synthetic.
+ line_entry.SetLine(42)
+ self.assertTrue(
+ line_entry.IsValid(),
+ "Line entry should be valid after setting line, even without address",
+ )
+ self.assertEqual(line_entry.GetLine(), 42)
+
+ # Set file and column.
+ file_spec = lldb.SBFileSpec(os.path.join(self.getSourceDir(), "test.cpp"), True)
+ line_entry.SetFileSpec(file_spec)
+ line_entry.SetColumn(10)
+
+ self.assertEqual(line_entry.GetColumn(), 10)
+ self.assertEqual(line_entry.GetFileSpec().GetFilename(), "test.cpp")
+
+ # Verify address range is still invalid (synthetic).
+ start_addr = line_entry.GetStartAddress()
+ self.assertFalse(
+ start_addr.IsValid(), "Synthetic line entry should not have valid address"
+ )
+
+ # Test setting synthetic line entry in symbol context.
+ sym_ctx = lldb.SBSymbolContext()
+ sym_ctx.SetLineEntry(line_entry)
+
+ retrieved_line_entry = sym_ctx.GetLineEntry()
+ self.assertTrue(
+ retrieved_line_entry.IsValid(), "Retrieved line entry should be valid"
+ )
+ self.assertEqual(retrieved_line_entry.GetLine(), 42)
+ self.assertEqual(retrieved_line_entry.GetColumn(), 10)
+ self.assertEqual(retrieved_line_entry.GetFileSpec().GetFilename(), "test.cpp")
+
+ def test_line_entry_validity_without_address(self):
+ """Test that line entries created via SBAPI are valid without addresses."""
+
+ line_entry = lldb.SBLineEntry()
+
+ # Initially invalid.
+ self.assertFalse(line_entry.IsValid())
+
+ # Still invalid with just a file spec.
+ file_spec = lldb.SBFileSpec("foo.cpp", True)
+ line_entry.SetFileSpec(file_spec)
+ self.assertFalse(
+ line_entry.IsValid(), "Line entry should be invalid without line number"
+ )
+
+ # Valid once line number is set (marks as synthetic).
+ line_entry.SetLine(100)
+ self.assertTrue(
+ line_entry.IsValid(), "Line entry should be valid with line number set"
+ )
+
+ # Verify no valid address range.
+ self.assertFalse(line_entry.GetStartAddress().IsValid())
+ self.assertFalse(line_entry.GetEndAddress().IsValid())
+
+ def test_line_entry_column(self):
+ """Test setting and getting column information on synthetic line entries."""
+
+ line_entry = lldb.SBLineEntry()
+ line_entry.SetLine(50)
+
+ # Default column should be 0.
+ self.assertEqual(line_entry.GetColumn(), 0)
+
+ # Set column.
+ line_entry.SetColumn(25)
+ self.assertEqual(line_entry.GetColumn(), 25)
+
+ # Verify line entry is still valid.
+ self.assertTrue(line_entry.IsValid())
+
+ def test_non_synthetic_line_entry_requires_line_number(self):
+ """Test that non-synthetic line entries with addresses still require a line number to be valid."""
+
+ # A line entry is always invalid without a line number, regardless of whether it has an address.
+ line_entry = lldb.SBLineEntry()
+ self.assertFalse(
+ line_entry.IsValid(), "Line entry should be invalid without line number"
+ )
+
+ # Even with a file spec, it's still invalid.
+ file_spec = lldb.SBFileSpec("test.cpp", True)
+ line_entry.SetFileSpec(file_spec)
+ self.assertFalse(
+ line_entry.IsValid(), "Line entry should be invalid without line number"
+ )
+
+ # Only after setting a line number does it become valid.
+ line_entry.SetLine(42)
+ self.assertTrue(
+ line_entry.IsValid(), "Line entry should be valid with line number"
+ )
+
+ def test_symbol_context_with_synthetic_line_entry(self):
+ """Test that SBSymbolContext correctly stores and retrieves synthetic line entries."""
+
+ # Create a synthetic line entry.
+ line_entry = lldb.SBLineEntry()
+ line_entry.SetLine(123)
+ line_entry.SetColumn(45)
+ file_spec = lldb.SBFileSpec("source.cpp", True)
+ line_entry.SetFileSpec(file_spec)
+
+ # Create symbol context and set line entry.
+ sym_ctx = lldb.SBSymbolContext()
+ sym_ctx.SetLineEntry(line_entry)
+
+ # Retrieve and verify.
+ retrieved = sym_ctx.GetLineEntry()
+ self.assertTrue(retrieved.IsValid())
+ self.assertEqual(retrieved.GetLine(), 123)
+ self.assertEqual(retrieved.GetColumn(), 45)
+ self.assertEqual(retrieved.GetFileSpec().GetFilename(), "source.cpp")
+
+ # Verify it's still synthetic (no valid address).
+ self.assertFalse(retrieved.GetStartAddress().IsValid())
More information about the lldb-commits
mailing list