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    return val.summary if val else 'None'
142
143class OptionalSynthProvider:
144    """Provides deref support to llvm::Optional<T>"""
145    def __init__(self, valobj, internal_dict):
146        self.valobj = valobj
147
148    def num_children(self):
149        return self.valobj.num_children
150
151    def get_child_index(self, name):
152        if name == '$$dereference$$':
153            return self.valobj.num_children
154        return self.valobj.GetIndexOfChildWithName(name)
155
156    def get_child_at_index(self, index):
157        if index < self.valobj.num_children:
158            return self.valobj.GetChildAtIndex(index)
159        return GetOptionalValue(self.valobj) or lldb.SBValue()
160
161def SmallStringSummaryProvider(valobj, internal_dict):
162    num_elements = valobj.GetNumChildren()
163    res = "\""
164    for i in range(0, num_elements):
165        c = valobj.GetChildAtIndex(i).GetValue()
166        if c:
167            res += c.strip("'")
168    res += "\""
169    return res
170
171
172def StringRefSummaryProvider(valobj, internal_dict):
173    if valobj.GetNumChildren() == 2:
174        # StringRef's are also used to point at binary blobs in memory,
175        # so filter out suspiciously long strings.
176        max_length = 1024
177        actual_length = valobj.GetChildAtIndex(1).GetValueAsUnsigned()
178        truncate = actual_length > max_length
179        length = min(max_length, actual_length)
180        if length == 0:
181            return '""'
182
183        data = valobj.GetChildAtIndex(0).GetPointeeData(item_count=length)
184        error = lldb.SBError()
185        string = data.ReadRawData(error, 0, data.GetByteSize()).decode()
186        if error.Fail():
187            return "<error: %s>" % error.description
188
189        # json.dumps conveniently escapes the string for us.
190        string = json.dumps(string)
191        if truncate:
192            string += "..."
193        return string
194    return None
195
196
197def ConstStringSummaryProvider(valobj, internal_dict):
198    if valobj.GetNumChildren() == 1:
199        return valobj.GetChildAtIndex(0).GetSummary()
200    return ""
201
202
203def get_expression_path(val):
204    stream = lldb.SBStream()
205    if not val.GetExpressionPath(stream):
206        return None
207    return stream.GetData()
208
209
210class PointerIntPairSynthProvider:
211    def __init__(self, valobj, internal_dict):
212        self.valobj = valobj
213        self.update()
214
215    def num_children(self):
216        return 2
217
218    def get_child_index(self, name):
219        if name == 'Pointer':
220            return 0
221        if name == 'Int':
222            return 1
223        return None
224
225    def get_child_at_index(self, index):
226        expr_path = get_expression_path(self.valobj)
227        if index == 0:
228            return self.valobj.CreateValueFromExpression('Pointer', f'({self.pointer_ty.name}){expr_path}.getPointer()')
229        if index == 1:
230            return self.valobj.CreateValueFromExpression('Int', f'({self.int_ty.name}){expr_path}.getInt()')
231        return None
232
233    def update(self):
234        self.pointer_ty = self.valobj.GetType().GetTemplateArgumentType(0)
235        self.int_ty = self.valobj.GetType().GetTemplateArgumentType(2)
236
237
238def parse_template_parameters(typename):
239    """
240    LLDB doesn't support template parameter packs, so let's parse them manually.
241    """
242    result = []
243    start = typename.find('<')
244    end = typename.rfind('>')
245    if start < 1 or end < 2 or end - start < 2:
246        return result
247
248    nesting_level = 0
249    current_parameter_start = start + 1
250
251    for i in range(start + 1, end + 1):
252        c = typename[i]
253        if c == '<':
254            nesting_level += 1
255        elif c == '>':
256            nesting_level -= 1
257        elif c == ',' and nesting_level == 0:
258            result.append(typename[current_parameter_start:i].strip())
259            current_parameter_start = i + 1
260
261    result.append(typename[current_parameter_start:i].strip())
262
263    return result
264
265
266class PointerUnionSynthProvider:
267    def __init__(self, valobj, internal_dict):
268        self.valobj = valobj
269        self.update()
270
271    def num_children(self):
272        return 1
273
274    def get_child_index(self, name):
275        if name == 'Ptr':
276            return 0
277        return None
278
279    def get_child_at_index(self, index):
280        if index != 0:
281            return None
282        ptr_type_name = self.template_args[self.active_type_tag]
283        return self.valobj.CreateValueFromExpression('Ptr', f'({ptr_type_name}){self.val_expr_path}.getPointer()')
284
285    def update(self):
286        self.pointer_int_pair = self.valobj.GetChildMemberWithName('Val')
287        self.val_expr_path = get_expression_path(self.valobj.GetChildMemberWithName('Val'))
288        self.active_type_tag = self.valobj.CreateValueFromExpression('', f'(int){self.val_expr_path}.getInt()').GetValueAsSigned()
289        self.template_args = parse_template_parameters(self.valobj.GetType().name)
290