1"""
2LLDB Formatters for LLVM data types.
3
4Load into LLDB with 'command script import /path/to/lldbDataFormatters.py'
5"""
6
7import lldb
8import json
9
10def __lldb_init_module(debugger, internal_dict):
11    debugger.HandleCommand('type category define -e llvm -l c++')
12    debugger.HandleCommand('type synthetic add -w llvm '
13                           '-l lldbDataFormatters.SmallVectorSynthProvider '
14                           '-x "^llvm::SmallVectorImpl<.+>$"')
15    debugger.HandleCommand('type summary add -w llvm '
16                           '-s "size=${svar%#}" '
17                           '-x "^llvm::SmallVectorImpl<.+>$"')
18    debugger.HandleCommand('type synthetic add -w llvm '
19                           '-l lldbDataFormatters.SmallVectorSynthProvider '
20                           '-x "^llvm::SmallVector<.+,.+>$"')
21    debugger.HandleCommand('type summary add -w llvm '
22                           '-s "size=${svar%#}" '
23                           '-x "^llvm::SmallVector<.+,.+>$"')
24    debugger.HandleCommand('type synthetic add -w llvm '
25                           '-l lldbDataFormatters.ArrayRefSynthProvider '
26                           '-x "^llvm::ArrayRef<.+>$"')
27    debugger.HandleCommand('type summary add -w llvm '
28                           '-s "size=${svar%#}" '
29                           '-x "^llvm::ArrayRef<.+>$"')
30    debugger.HandleCommand('type synthetic add -w llvm '
31                           '-l lldbDataFormatters.OptionalSynthProvider '
32                           '-x "^llvm::Optional<.+>$"')
33    debugger.HandleCommand('type summary add -w llvm '
34                           '-F lldbDataFormatters.OptionalSummaryProvider '
35                           '-x "^llvm::Optional<.+>$"')
36    debugger.HandleCommand('type summary add -w llvm '
37                           '-F lldbDataFormatters.SmallStringSummaryProvider '
38                           '-x "^llvm::SmallString<.+>$"')
39    debugger.HandleCommand('type summary add -w llvm '
40                           '-F lldbDataFormatters.StringRefSummaryProvider '
41                           '-x "^llvm::StringRef$"')
42    debugger.HandleCommand('type summary add -w llvm '
43                           '-F lldbDataFormatters.ConstStringSummaryProvider '
44                           '-x "^lldb_private::ConstString$"')
45    debugger.HandleCommand('type synthetic add -w llvm '
46                           '-l lldbDataFormatters.PointerIntPairSynthProvider '
47                           '-x "^llvm::PointerIntPair<.+>$"')
48    debugger.HandleCommand('type synthetic add -w llvm '
49                           '-l lldbDataFormatters.PointerUnionSynthProvider '
50                           '-x "^llvm::PointerUnion<.+>$"')
51
52
53# Pretty printer for llvm::SmallVector/llvm::SmallVectorImpl
54class SmallVectorSynthProvider:
55    def __init__(self, valobj, internal_dict):
56        self.valobj = valobj;
57        self.update() # initialize this provider
58
59    def num_children(self):
60        return self.size.GetValueAsUnsigned(0)
61
62    def get_child_index(self, name):
63        try:
64            return int(name.lstrip('[').rstrip(']'))
65        except:
66            return -1;
67
68    def get_child_at_index(self, index):
69        # Do bounds checking.
70        if index < 0:
71            return None
72        if index >= self.num_children():
73            return None;
74
75        offset = index * self.type_size
76        return self.begin.CreateChildAtOffset('['+str(index)+']',
77                                              offset, self.data_type)
78
79    def update(self):
80        self.begin = self.valobj.GetChildMemberWithName('BeginX')
81        self.size = self.valobj.GetChildMemberWithName('Size')
82        the_type = self.valobj.GetType()
83        # If this is a reference type we have to dereference it to get to the
84        # template parameter.
85        if the_type.IsReferenceType():
86            the_type = the_type.GetDereferencedType()
87
88        self.data_type = the_type.GetTemplateArgumentType(0)
89        self.type_size = self.data_type.GetByteSize()
90        assert self.type_size != 0
91
92class ArrayRefSynthProvider:
93    """ Provider for llvm::ArrayRef """
94    def __init__(self, valobj, internal_dict):
95        self.valobj = valobj;
96        self.update() # initialize this provider
97
98    def num_children(self):
99        return self.length
100
101    def get_child_index(self, name):
102        try:
103            return int(name.lstrip('[').rstrip(']'))
104        except:
105            return -1;
106
107    def get_child_at_index(self, index):
108        if index < 0 or index >= self.num_children():
109            return None;
110        offset = index * self.type_size
111        return self.data.CreateChildAtOffset('[' + str(index) + ']',
112                                             offset, self.data_type)
113
114    def update(self):
115        self.data = self.valobj.GetChildMemberWithName('Data')
116        length_obj = self.valobj.GetChildMemberWithName('Length')
117        self.length = length_obj.GetValueAsUnsigned(0)
118        self.data_type = self.data.GetType().GetPointeeType()
119        self.type_size = self.data_type.GetByteSize()
120        assert self.type_size != 0
121
122def GetOptionalValue(valobj):
123    storage = valobj.GetChildMemberWithName('Storage')
124    if not storage:
125        storage = valobj
126
127    failure = 2
128    hasVal = storage.GetChildMemberWithName('hasVal').GetValueAsUnsigned(failure)
129    if hasVal == failure:
130        return '<could not read llvm::Optional>'
131
132    if hasVal == 0:
133        return None
134
135    underlying_type = storage.GetType().GetTemplateArgumentType(0)
136    storage = storage.GetChildMemberWithName('value')
137    return storage.Cast(underlying_type)
138
139def OptionalSummaryProvider(valobj, internal_dict):
140    val = GetOptionalValue(valobj)
141    if val is None:
142        return 'None'
143    if val.summary:
144        return val.summary
145    return ''
146
147class OptionalSynthProvider:
148    """Provides deref support to llvm::Optional<T>"""
149    def __init__(self, valobj, internal_dict):
150        self.valobj = valobj
151
152    def num_children(self):
153        return self.valobj.num_children
154
155    def get_child_index(self, name):
156        if name == '$$dereference$$':
157            return self.valobj.num_children
158        return self.valobj.GetIndexOfChildWithName(name)
159
160    def get_child_at_index(self, index):
161        if index < self.valobj.num_children:
162            return self.valobj.GetChildAtIndex(index)
163        return GetOptionalValue(self.valobj) or lldb.SBValue()
164
165def SmallStringSummaryProvider(valobj, internal_dict):
166    num_elements = valobj.GetNumChildren()
167    res = "\""
168    for i in range(0, num_elements):
169        c = valobj.GetChildAtIndex(i).GetValue()
170        if c:
171            res += c.strip("'")
172    res += "\""
173    return res
174
175
176def StringRefSummaryProvider(valobj, internal_dict):
177    if valobj.GetNumChildren() == 2:
178        # StringRef's are also used to point at binary blobs in memory,
179        # so filter out suspiciously long strings.
180        max_length = 1024
181        actual_length = valobj.GetChildAtIndex(1).GetValueAsUnsigned()
182        truncate = actual_length > max_length
183        length = min(max_length, actual_length)
184        if length == 0:
185            return '""'
186
187        data = valobj.GetChildAtIndex(0).GetPointeeData(item_count=length)
188        error = lldb.SBError()
189        string = data.ReadRawData(error, 0, data.GetByteSize()).decode()
190        if error.Fail():
191            return "<error: %s>" % error.description
192
193        # json.dumps conveniently escapes the string for us.
194        string = json.dumps(string)
195        if truncate:
196            string += "..."
197        return string
198    return None
199
200
201def ConstStringSummaryProvider(valobj, internal_dict):
202    if valobj.GetNumChildren() == 1:
203        return valobj.GetChildAtIndex(0).GetSummary()
204    return ""
205
206
207def get_expression_path(val):
208    stream = lldb.SBStream()
209    if not val.GetExpressionPath(stream):
210        return None
211    return stream.GetData()
212
213
214class PointerIntPairSynthProvider:
215    def __init__(self, valobj, internal_dict):
216        self.valobj = valobj
217        self.update()
218
219    def num_children(self):
220        return 2
221
222    def get_child_index(self, name):
223        if name == 'Pointer':
224            return 0
225        if name == 'Int':
226            return 1
227        return None
228
229    def get_child_at_index(self, index):
230        expr_path = get_expression_path(self.valobj)
231        if index == 0:
232            return self.valobj.CreateValueFromExpression('Pointer', f'({self.pointer_ty.name}){expr_path}.getPointer()')
233        if index == 1:
234            return self.valobj.CreateValueFromExpression('Int', f'({self.int_ty.name}){expr_path}.getInt()')
235        return None
236
237    def update(self):
238        self.pointer_ty = self.valobj.GetType().GetTemplateArgumentType(0)
239        self.int_ty = self.valobj.GetType().GetTemplateArgumentType(2)
240
241
242def parse_template_parameters(typename):
243    """
244    LLDB doesn't support template parameter packs, so let's parse them manually.
245    """
246    result = []
247    start = typename.find('<')
248    end = typename.rfind('>')
249    if start < 1 or end < 2 or end - start < 2:
250        return result
251
252    nesting_level = 0
253    current_parameter_start = start + 1
254
255    for i in range(start + 1, end + 1):
256        c = typename[i]
257        if c == '<':
258            nesting_level += 1
259        elif c == '>':
260            nesting_level -= 1
261        elif c == ',' and nesting_level == 0:
262            result.append(typename[current_parameter_start:i].strip())
263            current_parameter_start = i + 1
264
265    result.append(typename[current_parameter_start:i].strip())
266
267    return result
268
269
270class PointerUnionSynthProvider:
271    def __init__(self, valobj, internal_dict):
272        self.valobj = valobj
273        self.update()
274
275    def num_children(self):
276        return 1
277
278    def get_child_index(self, name):
279        if name == 'Ptr':
280            return 0
281        return None
282
283    def get_child_at_index(self, index):
284        if index != 0:
285            return None
286        ptr_type_name = self.template_args[self.active_type_tag]
287        return self.valobj.CreateValueFromExpression('Ptr', f'({ptr_type_name}){self.val_expr_path}.getPointer()')
288
289    def update(self):
290        self.pointer_int_pair = self.valobj.GetChildMemberWithName('Val')
291        self.val_expr_path = get_expression_path(self.valobj.GetChildMemberWithName('Val'))
292        self.active_type_tag = self.valobj.CreateValueFromExpression('', f'(int){self.val_expr_path}.getInt()').GetValueAsSigned()
293        self.template_args = parse_template_parameters(self.valobj.GetType().name)
294