summary refs log tree commit diff
path: root/src/etc/gdb_rust_pretty_printing.py
blob: fc4d15a5168db5ac561280790c8b9bb2f20c6487 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
# file at the top-level directory of this distribution and at
# http://rust-lang.org/COPYRIGHT.
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.

import gdb
import re

#===============================================================================
# GDB Pretty Printing Module for Rust
#===============================================================================


def register_printers(objfile):
    "Registers Rust pretty printers for the given objfile"
    objfile.pretty_printers.append(rust_pretty_printer_lookup_function)


def rust_pretty_printer_lookup_function(val):
    "Returns the correct Rust pretty printer for the given value if there is one"
    type_code = val.type.code

    if type_code == gdb.TYPE_CODE_STRUCT:
        struct_kind = classify_struct(val.type)

        if struct_kind == STRUCT_KIND_SLICE:
            return RustSlicePrinter(val)

        if struct_kind == STRUCT_KIND_STR_SLICE:
            return RustStringSlicePrinter(val)

        if struct_kind == STRUCT_KIND_STD_VEC:
            return RustStdVecPrinter(val)

        if struct_kind == STRUCT_KIND_STD_STRING:
            return RustStdStringPrinter(val)

        if struct_kind == STRUCT_KIND_TUPLE:
            return RustTuplePrinter(val)

        if struct_kind == STRUCT_KIND_TUPLE_STRUCT:
            return RustTupleStructPrinter(val, False)

        if struct_kind == STRUCT_KIND_CSTYLE_VARIANT:
            return RustCStyleEnumPrinter(val[get_field_at_index(val, 0)])

        if struct_kind == STRUCT_KIND_TUPLE_VARIANT:
            return RustTupleStructPrinter(val, True)

        if struct_kind == STRUCT_KIND_STRUCT_VARIANT:
            return RustStructPrinter(val, True)

        return RustStructPrinter(val, False)

    # Enum handling
    if type_code == gdb.TYPE_CODE_UNION:
        enum_members = list(val.type.fields())
        enum_member_count = len(enum_members)

        if enum_member_count == 0:
            return RustStructPrinter(val, False)

        if enum_member_count == 1:
            first_variant_name = enum_members[0].name
            if first_variant_name is None:
                # This is a singleton enum
                return rust_pretty_printer_lookup_function(val[enum_members[0]])
            else:
                assert first_variant_name.startswith("RUST$ENCODED$ENUM$")
                # This is a space-optimized enum.
                # This means this enum has only two states, and Rust uses one
                # of the fields somewhere in the struct to determine which of
                # the two states it's in. The location of the field is encoded
                # in the name as something like
                # RUST$ENCODED$ENUM$(num$)*name_of_zero_state
                last_separator_index = first_variant_name.rfind("$")
                start_index = len("RUST$ENCODED$ENUM$")
                disr_field_indices = first_variant_name[start_index:last_separator_index].split("$")
                disr_field_indices = [int(index) for index in disr_field_indices]

                sole_variant_val = val[enum_members[0]]
                discriminant = sole_variant_val
                for disr_field_index in disr_field_indices:
                    disr_field = get_field_at_index(discriminant, disr_field_index)
                    discriminant = discriminant[disr_field]

                # If the discriminant field is a fat pointer we have to consider the
                # first word as the true discriminant
                if discriminant.type.code == gdb.TYPE_CODE_STRUCT:
                    discriminant = discriminant[get_field_at_index(discriminant, 0)]

                if discriminant == 0:
                    null_variant_name = first_variant_name[last_separator_index + 1:]
                    return IdentityPrinter(null_variant_name)

                return rust_pretty_printer_lookup_function(sole_variant_val)

        # This is a regular enum, extract the discriminant
        discriminant_name, discriminant_val = extract_discriminant_value(val)
        return rust_pretty_printer_lookup_function(val[enum_members[discriminant_val]])

    # No pretty printer has been found
    return None

#=------------------------------------------------------------------------------
# Pretty Printer Classes
#=------------------------------------------------------------------------------


class RustStructPrinter:
    def __init__(self, val, hide_first_field):
        self.val = val
        self.hide_first_field = hide_first_field

    def to_string(self):
        return self.val.type.tag

    def children(self):
        cs = []
        for field in self.val.type.fields():
            field_name = field.name
            # Normally the field name is used as a key to access the field
            # value, because that's also supported in older versions of GDB...
            field_key = field_name
            if field_name is None:
                field_name = ""
                # ... but for fields without a name (as in tuples), we have to
                # fall back to the newer method of using the field object
                # directly as key. In older versions of GDB, this will just
                # fail.
                field_key = field
            name_value_tuple = (field_name, self.val[field_key])
            cs.append(name_value_tuple)

        if self.hide_first_field:
            cs = cs[1:]

        return cs


class RustTuplePrinter:
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return None

    def children(self):
        cs = []
        for field in self.val.type.fields():
            cs.append(("", self.val[field]))

        return cs

    def display_hint(self):
        return "array"


class RustTupleStructPrinter:
    def __init__(self, val, hide_first_field):
        self.val = val
        self.hide_first_field = hide_first_field

    def to_string(self):
        return self.val.type.tag

    def children(self):
        cs = []
        for field in self.val.type.fields():
            cs.append(("", self.val[field]))

        if self.hide_first_field:
            cs = cs[1:]

        return cs

    def display_hint(self):
        return "array"

class RustSlicePrinter:
    def __init__(self, val):
        self.val = val

    def display_hint(self):
        return "array"

    def to_string(self):
        length = int(self.val["length"])
        return self.val.type.tag + ("(len: %i)" % length)

    def children(self):
        cs = []
        length = int(self.val["length"])
        data_ptr = self.val["data_ptr"]
        assert data_ptr.type.code == gdb.TYPE_CODE_PTR
        pointee_type = data_ptr.type.target()

        for index in range(0, length):
            cs.append((str(index), (data_ptr + index).dereference()))

        return cs

class RustStringSlicePrinter:
    def __init__(self, val):
        self.val = val

    def to_string(self):
        slice_byte_len = self.val["length"]
        return '"%s"' % self.val["data_ptr"].string(encoding="utf-8", length=slice_byte_len)

class RustStdVecPrinter:
    def __init__(self, val):
        self.val = val

    def display_hint(self):
        return "array"

    def to_string(self):
        length = int(self.val["len"])
        cap = int(self.val["cap"])
        return self.val.type.tag + ("(len: %i, cap: %i)" % (length, cap))

    def children(self):
        cs = []
        (length, data_ptr) = extract_length_and_data_ptr_from_std_vec(self.val)
        pointee_type = data_ptr.type.target()

        for index in range(0, length):
            cs.append((str(index), (data_ptr + index).dereference()))
        return cs

class RustStdStringPrinter:
    def __init__(self, val):
        self.val = val

    def to_string(self):
        (length, data_ptr) = extract_length_and_data_ptr_from_std_vec(self.val["vec"])
        return '"%s"' % data_ptr.string(encoding="utf-8", length=length)


class RustCStyleEnumPrinter:
    def __init__(self, val):
        assert val.type.code == gdb.TYPE_CODE_ENUM
        self.val = val

    def to_string(self):
        return str(self.val)


class IdentityPrinter:
    def __init__(self, string):
        self.string = string

    def to_string(self):
        return self.string

STRUCT_KIND_REGULAR_STRUCT  = 0
STRUCT_KIND_TUPLE_STRUCT    = 1
STRUCT_KIND_TUPLE           = 2
STRUCT_KIND_TUPLE_VARIANT   = 3
STRUCT_KIND_STRUCT_VARIANT  = 4
STRUCT_KIND_CSTYLE_VARIANT  = 5
STRUCT_KIND_SLICE           = 6
STRUCT_KIND_STR_SLICE       = 7
STRUCT_KIND_STD_VEC         = 8
STRUCT_KIND_STD_STRING      = 9


def classify_struct(type):
    # print("\nclassify_struct: tag=%s\n" % type.tag)
    if type.tag == "&str":
        return STRUCT_KIND_STR_SLICE

    if type.tag.startswith("&[") and type.tag.endswith("]"):
        return STRUCT_KIND_SLICE

    fields = list(type.fields())
    field_count = len(fields)

    if field_count == 0:
        return STRUCT_KIND_REGULAR_STRUCT

    if (field_count == 3 and
        fields[0].name == "ptr" and
        fields[1].name == "len" and
        fields[2].name == "cap" and
        type.tag.startswith("Vec<")):
        return STRUCT_KIND_STD_VEC

    if (field_count == 1 and
        fields[0].name == "vec" and
        type.tag == "String"):
        return STRUCT_KIND_STD_STRING

    if fields[0].name == "RUST$ENUM$DISR":
        if field_count == 1:
            return STRUCT_KIND_CSTYLE_VARIANT
        elif all_fields_conform_to_tuple_field_naming(fields, 1):
            return STRUCT_KIND_TUPLE_VARIANT
        else:
            return STRUCT_KIND_STRUCT_VARIANT

    if all_fields_conform_to_tuple_field_naming(fields, 0):
        if type.tag.startswith("("):
            return STRUCT_KIND_TUPLE
        else:
            return STRUCT_KIND_TUPLE_STRUCT

    return STRUCT_KIND_REGULAR_STRUCT


def extract_discriminant_value(enum_val):
    assert enum_val.type.code == gdb.TYPE_CODE_UNION
    for variant_descriptor in enum_val.type.fields():
        variant_val = enum_val[variant_descriptor]
        for field in variant_val.type.fields():
            return (field.name, int(variant_val[field]))


def first_field(val):
    for field in val.type.fields():
        return field

def get_field_at_index(val, index):
    i = 0
    for field in val.type.fields():
        if i == index:
            return field
        i += 1
    return None

def all_fields_conform_to_tuple_field_naming(fields, start_index):
    for i in range(start_index, len(fields)):
        if (fields[i].name is None) or (re.match(r"__\d+$", fields[i].name) is None):
            return False
    return True

def extract_length_and_data_ptr_from_std_vec(vec_val):
    length = int(vec_val["len"])
    vec_ptr_val = vec_val["ptr"]
    unique_ptr_val = vec_ptr_val[first_field(vec_ptr_val)]
    data_ptr = unique_ptr_val[first_field(unique_ptr_val)]
    assert data_ptr.type.code == gdb.TYPE_CODE_PTR
    return (length, data_ptr)