summaryrefslogtreecommitdiff
path: root/tools/dev/gdb-py/svndbg/printers.py
blob: da041b49cca06ff2ce6765b13f6284a0e40ae741 (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
417
#!/usr/bin/env python
#
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#

import gdb
import re

import gdb.printing
from gdb.printing import RegexpCollectionPrettyPrinter


class TypedefRegexCollectionPrettyPrinter(RegexpCollectionPrettyPrinter):
    """Class for implementing a collection of pretty-printers, matching the
       type name to a regular expression.

       A pretty-printer in this collection will be used if the type of the
       value to be printed matches the printer's regular expression, or if
       the value is a pointer to and/or typedef to a type name that matches
       its regular expression.  The variations are tried in this order:

         1. the type name as known to the debugger (could be a 'typedef');
         2. the type after stripping off any number of layers of 'typedef';
         3. if it is a pointer, the pointed-to type;
         4. if it is a pointer, the pointed-to type minus some 'typedef's.

       In all cases, ignore 'const' and 'volatile' qualifiers.  When
       matching the pointed-to type, dereference the value or use 'None' if
       the value was a null pointer.

       This class is modeled on RegexpCollectionPrettyPrinter, which (in GDB
       7.3) matches on the base type's tag name and can't match a pointer
       type or any other type that doesn't have a tag name.
    """

    def __init__(self, name):
        super(TypedefRegexCollectionPrettyPrinter, self).__init__(name)

    def __call__(self, val):
        """Find and return an instantiation of a printer for VAL.
        """

        def lookup_type(type, val):
            """Return the first printer whose regular expression matches the
               name (tag name for struct/union/enum types) of TYPE, ignoring
               any 'const' or 'volatile' qualifiers.

               VAL is a gdb.Value, or may be None to indicate a dereferenced
               null pointer.  TYPE is the associated gdb.Type.
            """
            if type.code in [gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION,
                             gdb.TYPE_CODE_ENUM]:
                typename = type.tag
            else:
                typename = str(type.unqualified())
            for printer in self.subprinters:
                if printer.enabled and printer.compiled_re.search(typename):
                    return printer.gen_printer(val)

        def lookup_type_or_alias(type, val):
            """Return the first printer matching TYPE, or else if TYPE is a
               typedef then the first printer matching the aliased type.

               VAL is a gdb.Value, or may be None to indicate a dereferenced
               null pointer.  TYPE is the associated gdb.Type.
            """
            # First, look for a printer for the given (but unqualified) type.
            printer = lookup_type(type, val)
            if printer:
                return printer

            # If it's a typedef, look for a printer for the aliased type ...
            while type.code == gdb.TYPE_CODE_TYPEDEF:
                type = type.target()
                printer = lookup_type(type, val)
                if printer:
                    return printer

        # First, look for a printer for the given (but unqualified) type, or
        # its aliased type if it's a typedef.
        printer = lookup_type_or_alias(val.type, val)
        if printer:
            return printer

        # If it's a pointer, look for a printer for the pointed-to type.
        if val.type.code == gdb.TYPE_CODE_PTR:
            type = val.type.target()
            printer = lookup_type_or_alias(
                          type, val and val.dereference() or None)
            if printer:
                return printer

        # Cannot find a matching pretty printer in this collection.
        return None

class InferiorFunction:
    """A class whose instances are callable functions on the inferior
       process.
    """
    def __init__(self, function_name):
        self.function_name = function_name
        self.func = None

    def __call__(self, *args):
        if not self.func:
            self.func = gdb.parse_and_eval(self.function_name)
        return self.func(*args)

def children_as_map(children_iterator):
    """Convert an iteration of (key, value) pairs into the form required for
       a pretty-printer 'children' method when the display-hint is 'map'.
    """
    for k, v in children_iterator:
        yield 'key', k
        yield 'val', v


########################################################################

# Pretty-printing for APR library types.

# Some useful gdb.Type instances that can be initialized before any object
# files are loaded.
pvoidType = gdb.lookup_type('void').pointer()
cstringType = gdb.lookup_type('char').pointer()

# Some functions that resolve to calls into the inferior process.
apr_hash_count = InferiorFunction('apr_hash_count')
apr_hash_first = InferiorFunction('apr_hash_first')
apr_hash_next = InferiorFunction('apr_hash_next')
svn__apr_hash_index_key = InferiorFunction('svn__apr_hash_index_key')
svn__apr_hash_index_val = InferiorFunction('svn__apr_hash_index_val')

def children_of_apr_hash(hash_p, value_type=None):
    """Iterate over an 'apr_hash_t *' GDB value, in the way required for a
       pretty-printer 'children' method when the display-hint is 'map'.
       Cast the value pointers to VALUE_TYPE, or return values as '...' if
       VALUE_TYPE is None.
    """
    hi = apr_hash_first(0, hash_p)
    while (hi):
        k = svn__apr_hash_index_key(hi).reinterpret_cast(cstringType)
        if value_type:
            val = svn__apr_hash_index_val(hi).reinterpret_cast(value_type)
        else:
            val = '...'
        try:
            key = k.string()
        except:
            key = '<unreadable>'
        yield key, val
        hi = apr_hash_next(hi)

class AprHashPrinter:
    """for 'apr_hash_t' of 'char *' keys and unknown values"""
    def __init__(self, val):
        if val:
            self.hash_p = val.address
        else:
            self.hash_p = val

    def to_string(self):
        """Return a string to be displayed before children are displayed, or
           return None if we don't want any such.
        """
        if not self.hash_p:
            return 'NULL'
        return 'hash of ' + str(apr_hash_count(self.hash_p)) + ' items'

    def children(self):
        if not self.hash_p:
            return []
        return children_as_map(children_of_apr_hash(self.hash_p))

    def display_hint(self):
        return 'map'

def children_of_apr_array(array, value_type):
    """Iterate over an 'apr_array_header_t' GDB value, in the way required for
       a pretty-printer 'children' method when the display-hint is 'array'.
       Cast the values to VALUE_TYPE.
    """
    nelts = int(array['nelts'])
    elts = array['elts'].reinterpret_cast(value_type.pointer())
    for i in range(nelts):
        yield str(i), elts[i]

class AprArrayPrinter:
    """for 'apr_array_header_t' of unknown elements"""
    def __init__(self, val):
        self.array = val

    def to_string(self):
        if not self.array:
            return 'NULL'
        nelts = self.array['nelts']
        return 'array of ' + str(int(nelts)) + ' items'

    def children(self):
        # We can't display the children as we don't know their type.
        return []

    def display_hint(self):
        return 'array'

########################################################################

# Pretty-printing for Subversion libsvn_subr types.

class SvnBooleanPrinter:
    """for svn_boolean_t"""
    def __init__(self, val):
        self.val = val

    def to_string(self):
        if self.val is None:
            return '(NULL)'
        if self.val:
            return 'TRUE'
        else:
            return 'FALSE'

class SvnStringPrinter:
    """for svn_string_t"""
    def __init__(self, val):
        self.val = val

    def to_string(self):
        if not self.val:
            return 'NULL'

        data = self.val['data']
        len = int(self.val['len'])
        return data.string(length=len)

    def display_hint(self):
        if self.val:
            return 'string'

class SvnMergeRangePrinter:
    """for svn_merge_range_t"""
    def __init__(self, val):
        self.val = val

    def to_string(self):
        if not self.val:
            return 'NULL'

        r = self.val
        start = int(r['start'])
        end = int(r['end'])
        if start >= 0 and start < end:
            if start + 1 == end:
                rs = str(end)
            else:
                rs = str(start + 1) + '-' + str(end)
        elif end >= 0 and end < start:
            if start == end + 1:
                rs = '-' + str(start)
            else:
                rs = str(start) + '-' + str(end + 1)
        else:
            rs = '(INVALID: s=%d, e=%d)' % (start, end)
        if not r['inheritable']:
            rs += '*'
        return rs

    def display_hint(self):
        if self.val:
            return 'string'

class SvnRangelistPrinter:
    """for svn_rangelist_t"""
    def __init__(self, val):
        self.array = val
        self.svn_merge_range_t = gdb.lookup_type('svn_merge_range_t')

    def to_string(self):
        if not self.array:
            return 'NULL'

        s = ''
        for key, val in children_of_apr_array(self.array,
                                              self.svn_merge_range_t.pointer()):
            if s:
                s += ','
            s += SvnMergeRangePrinter(val).to_string()
        return s

    def display_hint(self):
        if self.array:
            return 'string'

class SvnMergeinfoPrinter:
    """for svn_mergeinfo_t"""
    def __init__(self, val):
        self.hash_p = val
        self.svn_rangelist_t = gdb.lookup_type('svn_rangelist_t')

    def to_string(self):
        if self.hash_p == 0:
            return 'NULL'

        s = ''
        for key, val in children_of_apr_hash(self.hash_p,
                                             self.svn_rangelist_t.pointer()):
            if s:
                s += '; '
            s += key + ':' + SvnRangelistPrinter(val).to_string()
        return '{ ' + s + ' }'

class SvnMergeinfoCatalogPrinter:
    """for svn_mergeinfo_catalog_t"""
    def __init__(self, val):
        self.hash_p = val
        self.svn_mergeinfo_t = gdb.lookup_type('svn_mergeinfo_t')

    def to_string(self):
        if self.hash_p == 0:
            return 'NULL'

        s = ''
        for key, val in children_of_apr_hash(self.hash_p,
                                             self.svn_mergeinfo_t):
            if s:
                s += ',\n  '
            s += "'" + key + "': " + SvnMergeinfoPrinter(val).to_string()
        return '{ ' + s + ' }'

########################################################################

# Pretty-printing for Subversion libsvn_client types.

class SvnPathrevPrinter:
    """for svn_client__pathrev_t"""
    def __init__(self, val):
        self.val = val

    def to_string(self):
        if not self.val:
            return 'NULL'

        rev = int(self.val['rev'])
        url = self.val['url'].string()
        repos_root_url = self.val['repos_root_url'].string()
        relpath = url[len(repos_root_url):]
        return "%s@%d" % (relpath, rev)

    def display_hint(self):
        if self.val:
            return 'string'


########################################################################

libapr_printer = None
libsvn_printer = None

def build_libsvn_printers():
    """Construct the pretty-printer objects."""

    global libapr_printer, libsvn_printer

    libapr_printer = TypedefRegexCollectionPrettyPrinter("libapr")
    libapr_printer.add_printer('apr_hash_t', r'^apr_hash_t$',
                               AprHashPrinter)
    libapr_printer.add_printer('apr_array_header_t', r'^apr_array_header_t$',
                               AprArrayPrinter)

    libsvn_printer = TypedefRegexCollectionPrettyPrinter("libsvn")
    libsvn_printer.add_printer('svn_boolean_t', r'^svn_boolean_t$',
                               SvnBooleanPrinter)
    libsvn_printer.add_printer('svn_string_t', r'^svn_string_t$',
                               SvnStringPrinter)
    libsvn_printer.add_printer('svn_client__pathrev_t', r'^svn_client__pathrev_t$',
                               SvnPathrevPrinter)
    libsvn_printer.add_printer('svn_merge_range_t', r'^svn_merge_range_t$',
                               SvnMergeRangePrinter)
    libsvn_printer.add_printer('svn_rangelist_t', r'^svn_rangelist_t$',
                               SvnRangelistPrinter)
    libsvn_printer.add_printer('svn_mergeinfo_t', r'^svn_mergeinfo_t$',
                               SvnMergeinfoPrinter)
    libsvn_printer.add_printer('svn_mergeinfo_catalog_t', r'^svn_mergeinfo_catalog_t$',
                               SvnMergeinfoCatalogPrinter)


def register_libsvn_printers(obj):
    """Register the pretty-printers for the object file OBJ."""

    global libapr_printer, libsvn_printer

    # Printers registered later take precedence.
    gdb.printing.register_pretty_printer(obj, libapr_printer)
    gdb.printing.register_pretty_printer(obj, libsvn_printer)


# Construct the pretty-printer objects, once, at GDB start-up time when this
# Python module is loaded.  (Registration happens later, once per object
# file.)
build_libsvn_printers()