Skip to content

Commit 3388ecd

Browse files
committed
fix(lldb): clang ast formatters
1 parent 886b6e8 commit 3388ecd

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed

bootstrap.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2509,6 +2509,7 @@ def generate_pretty_printer_configs(self):
25092509
lldbinit_content += f"# \n"
25102510
lldbinit_content += f"command script import {os.path.join(self.options.llvm_src_dir, 'llvm', 'utils', 'lldbDataFormatters.py').replace(os.sep, '/')}\n"
25112511
lldbinit_content += f"command script import {os.path.join(self.options.mrdocs_src_dir, 'share', 'lldb', 'mrdocs_formatters.py').replace(os.sep, '/')}\n"
2512+
lldbinit_content += f"command script import {os.path.join(self.options.mrdocs_src_dir, 'share', 'lldb', 'clang_ast_formatters.py').replace(os.sep, '/')}\n"
25122513
with open(lldbinit_path, "w") as f:
25132514
f.write(lldbinit_content)
25142515
print(f"Generated LLDB pretty printer configuration at '{lldbinit_path}'")

share/lldb/clang_ast_formatters.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#
2+
# Copyright (c) 2025 alandefreitas ([email protected])
3+
#
4+
# Distributed under the Boost Software License, Version 1.0.
5+
# https://www.boost.org/LICENSE_1_0.txt
6+
#
7+
8+
import lldb
9+
import re
10+
11+
12+
# ---------- tiny helpers ----------
13+
def _ctx(v):
14+
try:
15+
return v.Dereference() if v.TypeIsPointerType() else v
16+
except Exception:
17+
return v
18+
19+
def _eval_str(scope, code):
20+
opts = lldb.SBExpressionOptions()
21+
opts.SetLanguage(lldb.eLanguageTypeC_plus_plus)
22+
opts.SetCoerceResultToId(False)
23+
r = scope.EvaluateExpression(code, opts)
24+
if r and r.GetError() and r.GetError().Success():
25+
s = r.GetSummary() or r.GetValue()
26+
return s.strip('"') if s else None
27+
return None
28+
29+
def _strip_ptr_ref(t): # SBType -> SBType (no pointers/refs)
30+
try:
31+
# Drop references if possible
32+
dt = t.GetDereferencedType()
33+
if dt and dt.IsValid(): # only valid for refs
34+
t = dt
35+
# Drop pointers
36+
while t.IsPointerType():
37+
t = t.GetPointeeType()
38+
except Exception:
39+
pass
40+
return t
41+
42+
def _kind_from_type(valobj):
43+
t = _strip_ptr_ref(valobj.GetType())
44+
name = t.GetName() or ""
45+
m = re.search(r'clang::([A-Za-z_0-9]+)Decl', name)
46+
return m.group(1) if m else "Decl"
47+
48+
def _humanize_kind(kind: str) -> str:
49+
# Insert spaces at transitions: a|A, A|A a, digit|A; then lowercase.
50+
if not kind:
51+
return "decl"
52+
s = re.sub(r'(?<=[a-z0-9])(?=[A-Z])', ' ', kind)
53+
s = re.sub(r'(?<=[A-Z])(?=[A-Z][a-z])', ' ', s)
54+
return s.lower()
55+
56+
def _is_derived_from(sbtype: lldb.SBType, qualified_basename: str) -> bool:
57+
"""Walk base classes to see if sbtype derives from qualified_basename."""
58+
try:
59+
t = _strip_ptr_ref(sbtype)
60+
seen = set()
61+
q = [t]
62+
steps = 0
63+
while q and steps < 128:
64+
steps += 1
65+
cur = q.pop()
66+
n = cur.GetName() or ""
67+
if n == qualified_basename or n.endswith("::" + qualified_basename.split("::")[-1]):
68+
# Accept exact or namespaced matches
69+
if n.endswith(qualified_basename):
70+
return True
71+
num = cur.GetNumberOfDirectBaseClasses()
72+
for i in range(num):
73+
m = cur.GetDirectBaseClassAtIndex(i) # SBTypeMember
74+
bt = m.GetType()
75+
if bt and bt.IsValid():
76+
bn = bt.GetName() or ""
77+
if bn not in seen:
78+
seen.add(bn)
79+
q.append(bt)
80+
except Exception:
81+
pass
82+
return False
83+
84+
85+
# ---------- recognizers ----------
86+
def is_clang_nameddecl(sbtype, _dict):
87+
t = _strip_ptr_ref(sbtype)
88+
n = t.GetName() or ""
89+
if not n.startswith("clang::"):
90+
return False
91+
return _is_derived_from(t, "clang::NamedDecl")
92+
93+
def is_clang_decl_not_named(sbtype, _dict):
94+
t = _strip_ptr_ref(sbtype)
95+
n = t.GetName() or ""
96+
if not n.startswith("clang::"):
97+
return False
98+
# Must be a Decl, but NOT a NamedDecl
99+
return _is_derived_from(t, "clang::Decl") and not _is_derived_from(t, "clang::NamedDecl")
100+
101+
102+
# ---------- summaries ----------
103+
# NamedDecl (qualified name + humanized kind)
104+
def NamedDecl_summary(valobj, _dict):
105+
o = _ctx(valobj)
106+
107+
# Qualified name first
108+
q = _eval_str(o, "static_cast<const ::clang::NamedDecl*>(this)->getQualifiedNameAsString()")
109+
if not q:
110+
q = _eval_str(o, "static_cast<const ::clang::NamedDecl*>(this)->getNameAsString()")
111+
112+
# Anonymous namespace friendly label (for the rare case q is still empty)
113+
if not q:
114+
anon = _eval_str(
115+
o,
116+
"(static_cast<const ::clang::NamespaceDecl*>(this)->isAnonymousNamespace() "
117+
"? std::string(\"(anonymous namespace)\") : std::string())"
118+
)
119+
if anon:
120+
q = anon
121+
122+
# Decl kind → humanized lowercase
123+
kind = _eval_str(o, "std::string(getDeclKindName())") or _kind_from_type(valobj)
124+
hkind = _humanize_kind(kind)
125+
126+
return f"{q} ({hkind})" if q else f"<unnamed> ({hkind})"
127+
128+
129+
# Non-NamedDecl (unnamed) → "<unnamed> (humanized kind)"
130+
def UnnamedDecl_summary(valobj, _dict):
131+
o = _ctx(valobj)
132+
kind = _eval_str(o, "std::string(getDeclKindName())") or _kind_from_type(valobj)
133+
hkind = _humanize_kind(kind)
134+
return f"<unnamed> ({hkind})"
135+
136+
137+
def __lldb_init_module(debugger, _dict):
138+
# One rule for ALL NamedDecl-derived types
139+
debugger.HandleCommand(
140+
'type summary add '
141+
'--python-function clang_ast_formatters.NamedDecl_summary '
142+
'--recognizer-function clang_ast_formatters.is_clang_nameddecl'
143+
)
144+
# And a second rule for other Decl types (non-NamedDecl)
145+
debugger.HandleCommand(
146+
'type summary add '
147+
'--python-function clang_ast_formatters.UnnamedDecl_summary '
148+
'--recognizer-function clang_ast_formatters.is_clang_decl_not_named'
149+
)
150+
print("[clang-ast] NamedDecl and Unnamed Decl summaries registered via recognizers.")

0 commit comments

Comments
 (0)