summaryrefslogtreecommitdiff
path: root/src/etc/debugger_pretty_printers_common.py
blob: e64d863717da0019773a6eb0c861ebfd15d32aa1 (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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# Copyright 2015 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.

"""
This module provides an abstraction layer over common Rust pretty printing
functionality needed by both GDB and LLDB.
"""

import re

# Type codes that indicate the kind of type as it appears in DWARF debug
# information. This code alone is not sufficient to determine the Rust type.
# For example structs, tuples, fat pointers, or enum variants will all have
# DWARF_TYPE_CODE_STRUCT.
DWARF_TYPE_CODE_STRUCT = 1
DWARF_TYPE_CODE_UNION  = 2
DWARF_TYPE_CODE_PTR    = 3
DWARF_TYPE_CODE_ARRAY  = 4
DWARF_TYPE_CODE_ENUM   = 5

# These constants specify the most specific kind of type that could be
# determined for a given value.
TYPE_KIND_UNKNOWN           = -1
TYPE_KIND_EMPTY             = 0
TYPE_KIND_SLICE             = 1
TYPE_KIND_REGULAR_STRUCT    = 2
TYPE_KIND_TUPLE             = 3
TYPE_KIND_TUPLE_STRUCT      = 4
TYPE_KIND_CSTYLE_VARIANT    = 5
TYPE_KIND_TUPLE_VARIANT     = 6
TYPE_KIND_STRUCT_VARIANT    = 7
TYPE_KIND_STR_SLICE         = 8
TYPE_KIND_STD_VEC           = 9
TYPE_KIND_STD_STRING        = 10
TYPE_KIND_REGULAR_ENUM      = 11
TYPE_KIND_COMPRESSED_ENUM   = 12
TYPE_KIND_SINGLETON_ENUM    = 13
TYPE_KIND_CSTYLE_ENUM       = 14
TYPE_KIND_PTR               = 15
TYPE_KIND_FIXED_SIZE_VEC    = 16
TYPE_KIND_REGULAR_UNION     = 17
TYPE_KIND_OS_STRING         = 18
TYPE_KIND_STD_VECDEQUE      = 19
TYPE_KIND_STD_BTREESET      = 20

ENCODED_ENUM_PREFIX = "RUST$ENCODED$ENUM$"
ENUM_DISR_FIELD_NAME = "RUST$ENUM$DISR"

# Slice related constants
SLICE_FIELD_NAME_DATA_PTR = "data_ptr"
SLICE_FIELD_NAME_LENGTH = "length"
SLICE_FIELD_NAMES = [SLICE_FIELD_NAME_DATA_PTR, SLICE_FIELD_NAME_LENGTH]

# std::Vec<> related constants
STD_VEC_FIELD_NAME_LENGTH = "len"
STD_VEC_FIELD_NAME_BUF = "buf"
STD_VEC_FIELD_NAMES = [STD_VEC_FIELD_NAME_BUF,
                       STD_VEC_FIELD_NAME_LENGTH]

# std::collections::VecDeque<> related constants
STD_VECDEQUE_FIELD_NAME_TAIL = "tail"
STD_VECDEQUE_FIELD_NAME_HEAD = "head"
STD_VECDEQUE_FIELD_NAME_BUF = "buf"
STD_VECDEQUE_FIELD_NAMES = [STD_VECDEQUE_FIELD_NAME_TAIL,
                            STD_VECDEQUE_FIELD_NAME_HEAD,
                            STD_VECDEQUE_FIELD_NAME_BUF]

# std::collections::BTreeSet<> related constants
STD_BTREESET_FIELD_NAMES = ["map"]

# std::String related constants
STD_STRING_FIELD_NAMES = ["vec"]

# std::ffi::OsString related constants
OS_STRING_FIELD_NAMES = ["inner"]


class Type(object):
    """
    This class provides a common interface for type-oriented operations.
    Sub-classes are supposed to wrap a debugger-specific type-object and
    provide implementations for the abstract methods in this class.
    """

    def __init__(self):
        self.__type_kind = None

    def get_unqualified_type_name(self):
        """
        Implementations of this method should return the unqualified name of the
        type-object they are wrapping. Some examples:

        'int' -> 'int'
        'std::vec::Vec<std::string::String>' -> 'Vec<std::string::String>'
        '&std::option::Option<std::string::String>' -> '&std::option::Option<std::string::String>'

        As you can see, type arguments stay fully qualified.
        """
        raise NotImplementedError("Override this method")

    def get_dwarf_type_kind(self):
        """
        Implementations of this method should return the correct
        DWARF_TYPE_CODE_* value for the wrapped type-object.
        """
        raise NotImplementedError("Override this method")

    def get_fields(self):
        """
        Implementations of this method should return a list of field-objects of
        this type. For Rust-enums (i.e. with DWARF_TYPE_CODE_UNION) these field-
        objects represent the variants of the enum. Field-objects must have a
        `name` attribute that gives their name as specified in DWARF.
        """
        assert ((self.get_dwarf_type_kind() == DWARF_TYPE_CODE_STRUCT) or
                (self.get_dwarf_type_kind() == DWARF_TYPE_CODE_UNION))
        raise NotImplementedError("Override this method")

    def get_wrapped_value(self):
        """
        Returns the debugger-specific type-object wrapped by this object. This
        is sometimes needed for doing things like pointer-arithmetic in GDB.
        """
        raise NotImplementedError("Override this method")

    def get_type_kind(self):
        """This method returns the TYPE_KIND_* value for this type-object."""
        if self.__type_kind is None:
            dwarf_type_code = self.get_dwarf_type_kind()

            if dwarf_type_code == DWARF_TYPE_CODE_STRUCT:
                self.__type_kind = self.__classify_struct()
            elif dwarf_type_code == DWARF_TYPE_CODE_UNION:
                self.__type_kind = self.__classify_union()
            elif dwarf_type_code == DWARF_TYPE_CODE_PTR:
                self.__type_kind = TYPE_KIND_PTR
            elif dwarf_type_code == DWARF_TYPE_CODE_ARRAY:
                self.__type_kind = TYPE_KIND_FIXED_SIZE_VEC
            else:
                self.__type_kind = TYPE_KIND_UNKNOWN
        return self.__type_kind

    def __classify_struct(self):
        assert self.get_dwarf_type_kind() == DWARF_TYPE_CODE_STRUCT

        unqualified_type_name = self.get_unqualified_type_name()

        # STR SLICE
        if unqualified_type_name == "&str":
            return TYPE_KIND_STR_SLICE

        # REGULAR SLICE
        if (unqualified_type_name.startswith(("&[", "&mut [")) and
            unqualified_type_name.endswith("]") and
            self.__conforms_to_field_layout(SLICE_FIELD_NAMES)):
            return TYPE_KIND_SLICE

        fields = self.get_fields()
        field_count = len(fields)

        # EMPTY STRUCT
        if field_count == 0:
            return TYPE_KIND_EMPTY

        # STD VEC
        if (unqualified_type_name.startswith("Vec<") and
            self.__conforms_to_field_layout(STD_VEC_FIELD_NAMES)):
            return TYPE_KIND_STD_VEC

        # STD COLLECTION VECDEQUE
        if (unqualified_type_name.startswith("VecDeque<") and
            self.__conforms_to_field_layout(STD_VECDEQUE_FIELD_NAMES)):
            return TYPE_KIND_STD_VECDEQUE

        # STD COLLECTION BTREESET
        if (unqualified_type_name.startswith("BTreeSet<") and
                self.__conforms_to_field_layout(STD_BTREESET_FIELD_NAMES)):
            return TYPE_KIND_STD_BTREESET

        # STD STRING
        if (unqualified_type_name.startswith("String") and
            self.__conforms_to_field_layout(STD_STRING_FIELD_NAMES)):
            return TYPE_KIND_STD_STRING

        # OS STRING
        if (unqualified_type_name == "OsString" and
            self.__conforms_to_field_layout(OS_STRING_FIELD_NAMES)):
            return TYPE_KIND_OS_STRING

        # ENUM VARIANTS
        if fields[0].name == ENUM_DISR_FIELD_NAME:
            if field_count == 1:
                return TYPE_KIND_CSTYLE_VARIANT
            elif self.__all_fields_conform_to_tuple_field_naming(1):
                return TYPE_KIND_TUPLE_VARIANT
            else:
                return TYPE_KIND_STRUCT_VARIANT

        # TUPLE
        if self.__all_fields_conform_to_tuple_field_naming(0):
            if unqualified_type_name.startswith("("):
                return TYPE_KIND_TUPLE
            else:
                return TYPE_KIND_TUPLE_STRUCT

        # REGULAR STRUCT
        return TYPE_KIND_REGULAR_STRUCT


    def __classify_union(self):
        assert self.get_dwarf_type_kind() == DWARF_TYPE_CODE_UNION

        union_members = self.get_fields()
        union_member_count = len(union_members)
        if union_member_count == 0:
            return TYPE_KIND_EMPTY

        first_variant_name = union_members[0].name
        if first_variant_name is None:
            if union_member_count == 1:
                return TYPE_KIND_SINGLETON_ENUM
            else:
                return TYPE_KIND_REGULAR_ENUM
        elif first_variant_name.startswith(ENCODED_ENUM_PREFIX):
            assert union_member_count == 1
            return TYPE_KIND_COMPRESSED_ENUM
        else:
            return TYPE_KIND_REGULAR_UNION


    def __conforms_to_field_layout(self, expected_fields):
        actual_fields = self.get_fields()
        actual_field_count = len(actual_fields)

        if actual_field_count != len(expected_fields):
            return False

        for i in range(0, actual_field_count):
            if actual_fields[i].name != expected_fields[i]:
                return False

        return True

    def __all_fields_conform_to_tuple_field_naming(self, start_index):
        fields = self.get_fields()
        field_count = len(fields)

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


class Value(object):
    """
    This class provides a common interface for value-oriented operations.
    Sub-classes are supposed to wrap a debugger-specific value-object and
    provide implementations for the abstract methods in this class.
    """
    def __init__(self, ty):
        self.type = ty

    def get_child_at_index(self, index):
        """Returns the value of the field, array element or variant at the given index"""
        raise NotImplementedError("Override this method")

    def as_integer(self):
        """
        Try to convert the wrapped value into a Python integer. This should
        always succeed for values that are pointers or actual integers.
        """
        raise NotImplementedError("Override this method")

    def get_wrapped_value(self):
        """
        Returns the debugger-specific value-object wrapped by this object. This
        is sometimes needed for doing things like pointer-arithmetic in GDB.
        """
        raise NotImplementedError("Override this method")


class EncodedEnumInfo(object):
    """
    This class provides facilities for handling enum values with compressed
    encoding where a non-null field in one variant doubles as the discriminant.
    """

    def __init__(self, enum_val):
        assert enum_val.type.get_type_kind() == TYPE_KIND_COMPRESSED_ENUM
        variant_name = enum_val.type.get_fields()[0].name
        last_separator_index = variant_name.rfind("$")
        start_index = len(ENCODED_ENUM_PREFIX)
        indices_substring = variant_name[start_index:last_separator_index].split("$")
        self.__enum_val = enum_val
        self.__disr_field_indices = [int(index) for index in indices_substring]
        self.__null_variant_name = variant_name[last_separator_index + 1:]

    def is_null_variant(self):
        ty = self.__enum_val.type
        sole_variant_val = self.__enum_val.get_child_at_index(0)
        discriminant_val = sole_variant_val
        for disr_field_index in self.__disr_field_indices:
            discriminant_val = discriminant_val.get_child_at_index(disr_field_index)

        # If the discriminant field is a fat pointer we have to consider the
        # first word as the true discriminant
        if discriminant_val.type.get_dwarf_type_kind() == DWARF_TYPE_CODE_STRUCT:
            discriminant_val = discriminant_val.get_child_at_index(0)

        return discriminant_val.as_integer() == 0

    def get_non_null_variant_val(self):
        return self.__enum_val.get_child_at_index(0)

    def get_null_variant_name(self):
        return self.__null_variant_name


def get_discriminant_value_as_integer(enum_val):
    assert enum_val.type.get_dwarf_type_kind() == DWARF_TYPE_CODE_UNION
    # we can take any variant here because the discriminant has to be the same
    # for all of them.
    variant_val = enum_val.get_child_at_index(0)
    disr_val = variant_val.get_child_at_index(0)
    return disr_val.as_integer()


def extract_length_ptr_and_cap_from_std_vec(vec_val):
    assert vec_val.type.get_type_kind() == TYPE_KIND_STD_VEC
    length_field_index = STD_VEC_FIELD_NAMES.index(STD_VEC_FIELD_NAME_LENGTH)
    buf_field_index = STD_VEC_FIELD_NAMES.index(STD_VEC_FIELD_NAME_BUF)

    length = vec_val.get_child_at_index(length_field_index).as_integer()
    buf = vec_val.get_child_at_index(buf_field_index)

    vec_ptr_val = buf.get_child_at_index(0)
    capacity = buf.get_child_at_index(1).as_integer()
    unique_ptr_val = vec_ptr_val.get_child_at_index(0)
    data_ptr = unique_ptr_val.get_child_at_index(0)
    assert data_ptr.type.get_dwarf_type_kind() == DWARF_TYPE_CODE_PTR
    return (length, data_ptr, capacity)


def extract_tail_head_ptr_and_cap_from_std_vecdeque(vec_val):
    assert vec_val.type.get_type_kind() == TYPE_KIND_STD_VECDEQUE
    tail_field_index = STD_VECDEQUE_FIELD_NAMES.index(STD_VECDEQUE_FIELD_NAME_TAIL)
    head_field_index = STD_VECDEQUE_FIELD_NAMES.index(STD_VECDEQUE_FIELD_NAME_HEAD)
    buf_field_index = STD_VECDEQUE_FIELD_NAMES.index(STD_VECDEQUE_FIELD_NAME_BUF)

    tail = vec_val.get_child_at_index(tail_field_index).as_integer()
    head = vec_val.get_child_at_index(head_field_index).as_integer()
    buf = vec_val.get_child_at_index(buf_field_index)

    vec_ptr_val = buf.get_child_at_index(0)
    capacity = buf.get_child_at_index(1).as_integer()
    unique_ptr_val = vec_ptr_val.get_child_at_index(0)
    data_ptr = unique_ptr_val.get_child_at_index(0)
    assert data_ptr.type.get_dwarf_type_kind() == DWARF_TYPE_CODE_PTR
    return (tail, head, data_ptr, capacity)


def extract_length_and_ptr_from_std_btreeset(vec_val):
    assert vec_val.type.get_type_kind() == TYPE_KIND_STD_BTREESET
    map = vec_val.get_child_at_index(0)
    root = map.get_child_at_index(0)
    length = map.get_child_at_index(1).as_integer()
    node = root.get_child_at_index(0)
    ptr = node.get_child_at_index(0)
    unique_ptr_val = ptr.get_child_at_index(0)
    data_ptr = unique_ptr_val.get_child_at_index(0)
    assert data_ptr.type.get_dwarf_type_kind() == DWARF_TYPE_CODE_PTR
    return (length, data_ptr)


def extract_length_and_ptr_from_slice(slice_val):
    assert (slice_val.type.get_type_kind() == TYPE_KIND_SLICE or
            slice_val.type.get_type_kind() == TYPE_KIND_STR_SLICE)

    length_field_index = SLICE_FIELD_NAMES.index(SLICE_FIELD_NAME_LENGTH)
    ptr_field_index = SLICE_FIELD_NAMES.index(SLICE_FIELD_NAME_DATA_PTR)

    length = slice_val.get_child_at_index(length_field_index).as_integer()
    data_ptr = slice_val.get_child_at_index(ptr_field_index)

    assert data_ptr.type.get_dwarf_type_kind() == DWARF_TYPE_CODE_PTR
    return (length, data_ptr)

UNQUALIFIED_TYPE_MARKERS = frozenset(["(", "[", "&", "*"])

def extract_type_name(qualified_type_name):
    """Extracts the type name from a fully qualified path"""
    if qualified_type_name[0] in UNQUALIFIED_TYPE_MARKERS:
        return qualified_type_name

    end_of_search = qualified_type_name.find("<")
    if end_of_search < 0:
        end_of_search = len(qualified_type_name)

    index = qualified_type_name.rfind("::", 0, end_of_search)
    if index < 0:
        return qualified_type_name
    else:
        return qualified_type_name[index + 2:]

try:
    compat_str = unicode  # Python 2
except NameError:
    compat_str = str