Skip to content

Commit 1fcece5

Browse files
committed
[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. 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 <[email protected]>
1 parent e71717c commit 1fcece5

File tree

5 files changed

+168
-2
lines changed

5 files changed

+168
-2
lines changed

lldb/include/lldb/Symbol/LineEntry.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ struct LineEntry {
136136
/// The section offset address range for this line entry.
137137
AddressRange range;
138138

139+
/// This gets set for LineEntries created from the SBAPI, where we don't
140+
/// necessary have a valid address range. When set, LineEntry::IsValid doesn't
141+
/// check the `range` validity.
142+
bool is_synthetic;
143+
139144
/// The source file, possibly mapped by the target.source-map setting.
140145
SupportFileNSP file_sp;
141146

lldb/source/API/SBLineEntry.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ void SBLineEntry::SetLine(uint32_t line) {
132132
LLDB_INSTRUMENT_VA(this, line);
133133

134134
ref().line = line;
135+
if (!ref().range.IsValid())
136+
ref().is_synthetic = true;
135137
}
136138

137139
void SBLineEntry::SetColumn(uint32_t column) {

lldb/source/Symbol/LineEntry.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
using namespace lldb_private;
1515

1616
LineEntry::LineEntry()
17-
: range(), file_sp(std::make_shared<SupportFile>()),
17+
: range(), is_synthetic(false), file_sp(std::make_shared<SupportFile>()),
1818
original_file_sp(std::make_shared<SupportFile>()),
1919
is_start_of_statement(0), is_start_of_basic_block(0), is_prologue_end(0),
2020
is_epilogue_begin(0), is_terminal_entry(0) {}
@@ -33,7 +33,8 @@ void LineEntry::Clear() {
3333
}
3434

3535
bool LineEntry::IsValid() const {
36-
return range.GetBaseAddress().IsValid() && line != LLDB_INVALID_LINE_NUMBER;
36+
return (range.GetBaseAddress().IsValid() || is_synthetic) &&
37+
line != LLDB_INVALID_LINE_NUMBER;
3738
}
3839

3940
bool LineEntry::DumpStopContext(Stream *s, bool show_fullpaths) const {

lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,20 @@ def __init__(self, process, args):
7474
self.frames.append(DummyScriptedFrame(self, args, len(self.frames), "bar"))
7575
self.frames.append(DummyScriptedFrame(self, args, len(self.frames), "foo"))
7676

77+
cwd = os.path.dirname(os.path.abspath(__file__))
78+
79+
le = lldb.SBLineEntry()
80+
le.SetFileSpec(lldb.SBFileSpec(os.path.join(cwd, "baz.cpp"), True))
81+
le.SetLine(9)
82+
le.SetColumn(10)
83+
84+
sym_ctx = lldb.SBSymbolContext()
85+
sym_ctx.SetLineEntry(le)
86+
87+
self.frames.append(
88+
DummyScriptedFrame(self, args, len(self.frames), "baz", sym_ctx)
89+
)
90+
7791
def get_thread_id(self) -> int:
7892
return 0x19
7993

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
"""
2+
Test SBLineEntry APIs, particularly synthetic line entries.
3+
"""
4+
5+
import os
6+
import lldb
7+
from lldbsuite.test.decorators import *
8+
from lldbsuite.test.lldbtest import *
9+
from lldbsuite.test import lldbutil
10+
11+
12+
class SBLineEntryTestCase(TestBase):
13+
NO_DEBUG_INFO_TESTCASE = True
14+
15+
def test_synthetic_line_entry(self):
16+
"""Test that synthetic LineEntry objects (created via SBAPI) can be
17+
valid without a valid address range and can be set in SBSymbolContext."""
18+
19+
# Test creating a synthetic line entry via SBAPI
20+
line_entry = lldb.SBLineEntry()
21+
self.assertFalse(
22+
line_entry.IsValid(), "Default constructed line entry should be invalid"
23+
)
24+
25+
# Set line number - this should mark the line entry as synthetic
26+
line_entry.SetLine(42)
27+
self.assertTrue(
28+
line_entry.IsValid(),
29+
"Line entry should be valid after setting line, even without address",
30+
)
31+
self.assertEqual(line_entry.GetLine(), 42)
32+
33+
# Set file and column
34+
file_spec = lldb.SBFileSpec(os.path.join(self.getSourceDir(), "test.cpp"), True)
35+
line_entry.SetFileSpec(file_spec)
36+
line_entry.SetColumn(10)
37+
38+
self.assertEqual(line_entry.GetColumn(), 10)
39+
self.assertEqual(line_entry.GetFileSpec().GetFilename(), "test.cpp")
40+
41+
# Verify address range is still invalid (synthetic)
42+
start_addr = line_entry.GetStartAddress()
43+
self.assertFalse(
44+
start_addr.IsValid(), "Synthetic line entry should not have valid address"
45+
)
46+
47+
# Test setting synthetic line entry in symbol context
48+
sym_ctx = lldb.SBSymbolContext()
49+
sym_ctx.SetLineEntry(line_entry)
50+
51+
retrieved_line_entry = sym_ctx.GetLineEntry()
52+
self.assertTrue(
53+
retrieved_line_entry.IsValid(), "Retrieved line entry should be valid"
54+
)
55+
self.assertEqual(retrieved_line_entry.GetLine(), 42)
56+
self.assertEqual(retrieved_line_entry.GetColumn(), 10)
57+
self.assertEqual(retrieved_line_entry.GetFileSpec().GetFilename(), "test.cpp")
58+
59+
def test_line_entry_validity_without_address(self):
60+
"""Test that line entries created via SBAPI are valid without addresses."""
61+
62+
line_entry = lldb.SBLineEntry()
63+
64+
# Initially invalid
65+
self.assertFalse(line_entry.IsValid())
66+
67+
# Still invalid with just a file spec
68+
file_spec = lldb.SBFileSpec("foo.cpp", True)
69+
line_entry.SetFileSpec(file_spec)
70+
self.assertFalse(
71+
line_entry.IsValid(), "Line entry should be invalid without line number"
72+
)
73+
74+
# Valid once line number is set (marks as synthetic)
75+
line_entry.SetLine(100)
76+
self.assertTrue(
77+
line_entry.IsValid(), "Line entry should be valid with line number set"
78+
)
79+
80+
# Verify no valid address range
81+
self.assertFalse(line_entry.GetStartAddress().IsValid())
82+
self.assertFalse(line_entry.GetEndAddress().IsValid())
83+
84+
def test_line_entry_column(self):
85+
"""Test setting and getting column information on synthetic line entries."""
86+
87+
line_entry = lldb.SBLineEntry()
88+
line_entry.SetLine(50)
89+
90+
# Default column should be 0
91+
self.assertEqual(line_entry.GetColumn(), 0)
92+
93+
# Set column
94+
line_entry.SetColumn(25)
95+
self.assertEqual(line_entry.GetColumn(), 25)
96+
97+
# Verify line entry is still valid
98+
self.assertTrue(line_entry.IsValid())
99+
100+
def test_non_synthetic_line_entry_requires_line_number(self):
101+
"""Test that non-synthetic line entries with addresses still require a line number to be valid."""
102+
103+
# A line entry is always invalid without a line number, regardless of whether it has an address
104+
line_entry = lldb.SBLineEntry()
105+
self.assertFalse(
106+
line_entry.IsValid(), "Line entry should be invalid without line number"
107+
)
108+
109+
# Even with a file spec, it's still invalid
110+
file_spec = lldb.SBFileSpec("test.cpp", True)
111+
line_entry.SetFileSpec(file_spec)
112+
self.assertFalse(
113+
line_entry.IsValid(), "Line entry should be invalid without line number"
114+
)
115+
116+
# Only after setting a line number does it become valid
117+
line_entry.SetLine(42)
118+
self.assertTrue(
119+
line_entry.IsValid(), "Line entry should be valid with line number"
120+
)
121+
122+
def test_symbol_context_with_synthetic_line_entry(self):
123+
"""Test that SBSymbolContext correctly stores and retrieves synthetic line entries."""
124+
125+
# Create a synthetic line entry
126+
line_entry = lldb.SBLineEntry()
127+
line_entry.SetLine(123)
128+
line_entry.SetColumn(45)
129+
file_spec = lldb.SBFileSpec("source.cpp", True)
130+
line_entry.SetFileSpec(file_spec)
131+
132+
# Create symbol context and set line entry
133+
sym_ctx = lldb.SBSymbolContext()
134+
sym_ctx.SetLineEntry(line_entry)
135+
136+
# Retrieve and verify
137+
retrieved = sym_ctx.GetLineEntry()
138+
self.assertTrue(retrieved.IsValid())
139+
self.assertEqual(retrieved.GetLine(), 123)
140+
self.assertEqual(retrieved.GetColumn(), 45)
141+
self.assertEqual(retrieved.GetFileSpec().GetFilename(), "source.cpp")
142+
143+
# Verify it's still synthetic (no valid address)
144+
self.assertFalse(retrieved.GetStartAddress().IsValid())

0 commit comments

Comments
 (0)