1# Pretty-printer utilities.
2# Copyright (C) 2010-2024 Free Software Foundation, Inc.
3
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17"""Utilities for working with pretty-printers."""
18
19import itertools
20import re
21
22import gdb
23import gdb.types
24
25
26class PrettyPrinter(object):
27    """A basic pretty-printer.
28
29    Attributes:
30        name: A unique string among all printers for the context in which
31            it is defined (objfile, progspace, or global(gdb)), and should
32            meaningfully describe what can be pretty-printed.
33            E.g., "StringPiece" or "protobufs".
34        subprinters: An iterable object with each element having a `name'
35            attribute, and, potentially, "enabled" attribute.
36            Or this is None if there are no subprinters.
37        enabled: A boolean indicating if the printer is enabled.
38
39    Subprinters are for situations where "one" pretty-printer is actually a
40    collection of several printers.  E.g., The libstdc++ pretty-printer has
41    a pretty-printer for each of several different types, based on regexps.
42    """
43
44    # While one might want to push subprinters into the subclass, it's
45    # present here to formalize such support to simplify
46    # commands/pretty_printers.py.
47
48    def __init__(self, name, subprinters=None):
49        self.name = name
50        self.subprinters = subprinters
51        self.enabled = True
52
53    def __call__(self, val):
54        # The subclass must define this.
55        raise NotImplementedError("PrettyPrinter __call__")
56
57
58class SubPrettyPrinter(object):
59    """Baseclass for sub-pretty-printers.
60
61    Sub-pretty-printers needn't use this, but it formalizes what's needed.
62
63    Attributes:
64        name: The name of the subprinter.
65        enabled: A boolean indicating if the subprinter is enabled.
66    """
67
68    def __init__(self, name):
69        self.name = name
70        self.enabled = True
71
72
73def register_pretty_printer(obj, printer, replace=False):
74    """Register pretty-printer PRINTER with OBJ.
75
76    The printer is added to the front of the search list, thus one can override
77    an existing printer if one needs to.  Use a different name when overriding
78    an existing printer, otherwise an exception will be raised; multiple
79    printers with the same name are disallowed.
80
81    Arguments:
82        obj: Either an objfile, progspace, or None (in which case the printer
83            is registered globally).
84        printer: Either a function of one argument (old way) or any object
85            which has attributes: name, enabled, __call__.
86        replace: If True replace any existing copy of the printer.
87            Otherwise if the printer already exists raise an exception.
88
89    Returns:
90        Nothing.
91
92    Raises:
93        TypeError: A problem with the type of the printer.
94        ValueError: The printer's name contains a semicolon ";".
95        RuntimeError: A printer with the same name is already registered.
96
97    If the caller wants the printer to be listable and disableable, it must
98    follow the PrettyPrinter API.  This applies to the old way (functions) too.
99    If printer is an object, __call__ is a method of two arguments:
100    self, and the value to be pretty-printed.  See PrettyPrinter.
101    """
102
103    # Watch for both __name__ and name.
104    # Functions get the former for free, but we don't want to use an
105    # attribute named __foo__ for pretty-printers-as-objects.
106    # If printer has both, we use `name'.
107    if not hasattr(printer, "__name__") and not hasattr(printer, "name"):
108        raise TypeError("printer missing attribute: name")
109    if hasattr(printer, "name") and not hasattr(printer, "enabled"):
110        raise TypeError("printer missing attribute: enabled")
111    if not hasattr(printer, "__call__"):
112        raise TypeError("printer missing attribute: __call__")
113
114    if hasattr(printer, "name"):
115        name = printer.name
116    else:
117        name = printer.__name__
118    if obj is None or obj is gdb:
119        if gdb.parameter("verbose"):
120            gdb.write("Registering global %s pretty-printer ...\n" % name)
121        obj = gdb
122    else:
123        if gdb.parameter("verbose"):
124            gdb.write(
125                "Registering %s pretty-printer for %s ...\n" % (name, obj.filename)
126            )
127
128    # Printers implemented as functions are old-style.  In order to not risk
129    # breaking anything we do not check __name__ here.
130    if hasattr(printer, "name"):
131        if not isinstance(printer.name, str):
132            raise TypeError("printer name is not a string")
133        # If printer provides a name, make sure it doesn't contain ";".
134        # Semicolon is used by the info/enable/disable pretty-printer commands
135        # to delimit subprinters.
136        if printer.name.find(";") >= 0:
137            raise ValueError("semicolon ';' in printer name")
138        # Also make sure the name is unique.
139        # Alas, we can't do the same for functions and __name__, they could
140        # all have a canonical name like "lookup_function".
141        # PERF: gdb records printers in a list, making this inefficient.
142        i = 0
143        for p in obj.pretty_printers:
144            if hasattr(p, "name") and p.name == printer.name:
145                if replace:
146                    del obj.pretty_printers[i]
147                    break
148                else:
149                    raise RuntimeError(
150                        "pretty-printer already registered: %s" % printer.name
151                    )
152            i = i + 1
153
154    obj.pretty_printers.insert(0, printer)
155
156
157class RegexpCollectionPrettyPrinter(PrettyPrinter):
158    """Class for implementing a collection of regular-expression based pretty-printers.
159
160    Intended usage:
161
162    pretty_printer = RegexpCollectionPrettyPrinter("my_library")
163    pretty_printer.add_printer("myclass1", "^myclass1$", MyClass1Printer)
164    ...
165    pretty_printer.add_printer("myclassN", "^myclassN$", MyClassNPrinter)
166    register_pretty_printer(obj, pretty_printer)
167    """
168
169    class RegexpSubprinter(SubPrettyPrinter):
170        def __init__(self, name, regexp, gen_printer):
171            super(RegexpCollectionPrettyPrinter.RegexpSubprinter, self).__init__(name)
172            self.regexp = regexp
173            self.gen_printer = gen_printer
174            self.compiled_re = re.compile(regexp)
175
176    def __init__(self, name):
177        super(RegexpCollectionPrettyPrinter, self).__init__(name, [])
178
179    def add_printer(self, name, regexp, gen_printer):
180        """Add a printer to the list.
181
182        The printer is added to the end of the list.
183
184        Arguments:
185            name: The name of the subprinter.
186            regexp: The regular expression, as a string.
187            gen_printer: A function/method that given a value returns an
188                object to pretty-print it.
189
190        Returns:
191            Nothing.
192        """
193
194        # NOTE: A previous version made the name of each printer the regexp.
195        # That makes it awkward to pass to the enable/disable commands (it's
196        # cumbersome to make a regexp of a regexp).  So now the name is a
197        # separate parameter.
198
199        self.subprinters.append(self.RegexpSubprinter(name, regexp, gen_printer))
200
201    def __call__(self, val):
202        """Lookup the pretty-printer for the provided value."""
203
204        # Get the type name.
205        typename = gdb.types.get_basic_type(val.type).tag
206        if not typename:
207            typename = val.type.name
208        if not typename:
209            return None
210
211        # Iterate over table of type regexps to determine
212        # if a printer is registered for that type.
213        # Return an instantiation of the printer if found.
214        for printer in self.subprinters:
215            if printer.enabled and printer.compiled_re.search(typename):
216                return printer.gen_printer(val)
217
218        # Cannot find a pretty printer.  Return None.
219        return None
220
221
222# A helper class for printing enum types.  This class is instantiated
223# with a list of enumerators to print a particular Value.
224class _EnumInstance(gdb.ValuePrinter):
225    def __init__(self, enumerators, val):
226        self.__enumerators = enumerators
227        self.__val = val
228
229    def to_string(self):
230        flag_list = []
231        v = int(self.__val)
232        any_found = False
233        for e_name, e_value in self.__enumerators:
234            if v & e_value != 0:
235                flag_list.append(e_name)
236                v = v & ~e_value
237                any_found = True
238        if not any_found or v != 0:
239            # Leftover value.
240            flag_list.append("<unknown: 0x%x>" % v)
241        return "0x%x [%s]" % (int(self.__val), " | ".join(flag_list))
242
243
244class FlagEnumerationPrinter(PrettyPrinter):
245    """A pretty-printer which can be used to print a flag-style enumeration.
246    A flag-style enumeration is one where the enumerators are or'd
247    together to create values.  The new printer will print these
248    symbolically using '|' notation.  The printer must be registered
249    manually.  This printer is most useful when an enum is flag-like,
250    but has some overlap.  GDB's built-in printing will not handle
251    this case, but this printer will attempt to."""
252
253    def __init__(self, enum_type):
254        super(FlagEnumerationPrinter, self).__init__(enum_type)
255        self.initialized = False
256
257    def __call__(self, val):
258        if not self.initialized:
259            self.initialized = True
260            flags = gdb.lookup_type(self.name)
261            self.enumerators = []
262            for field in flags.fields():
263                self.enumerators.append((field.name, field.enumval))
264            # Sorting the enumerators by value usually does the right
265            # thing.
266            self.enumerators.sort(key=lambda x: x[1])
267
268        if self.enabled:
269            return _EnumInstance(self.enumerators, val)
270        else:
271            return None
272
273
274class NoOpScalarPrinter(gdb.ValuePrinter):
275    """A no-op pretty printer that wraps a scalar value."""
276
277    def __init__(self, value):
278        self.__value = value
279
280    def to_string(self):
281        return self.__value.format_string(raw=True)
282
283
284class NoOpPointerReferencePrinter(gdb.ValuePrinter):
285    """A no-op pretty printer that wraps a pointer or reference."""
286
287    def __init__(self, value):
288        self.__value = value
289
290    def to_string(self):
291        return self.__value.format_string(deref_refs=False)
292
293    def num_children(self):
294        return 1
295
296    def child(self, i):
297        return "value", self.__value.referenced_value()
298
299    def children(self):
300        yield "value", self.__value.referenced_value()
301
302
303class NoOpArrayPrinter(gdb.ValuePrinter):
304    """A no-op pretty printer that wraps an array value."""
305
306    def __init__(self, ty, value):
307        self.__value = value
308        (low, high) = ty.range()
309        # In Ada, an array can have an index type that is a
310        # non-contiguous enum.  In this case the indexing must be done
311        # by using the indices into the enum type, not the underlying
312        # integer values.
313        range_type = ty.fields()[0].type
314        if range_type.target().code == gdb.TYPE_CODE_ENUM:
315            e_values = range_type.target().fields()
316            # Drop any values before LOW.
317            e_values = itertools.dropwhile(lambda x: x.enumval < low, e_values)
318            # Drop any values after HIGH.
319            e_values = itertools.takewhile(lambda x: x.enumval <= high, e_values)
320            low = 0
321            high = len(list(e_values)) - 1
322        self.__low = low
323        self.__high = high
324
325    def to_string(self):
326        return ""
327
328    def display_hint(self):
329        return "array"
330
331    def num_children(self):
332        return self.__high - self.__low + 1
333
334    def child(self, i):
335        return (self.__low + i, self.__value[self.__low + i])
336
337    def children(self):
338        for i in range(self.__low, self.__high + 1):
339            yield (i, self.__value[i])
340
341
342class NoOpStructPrinter(gdb.ValuePrinter):
343    """A no-op pretty printer that wraps a struct or union value."""
344
345    def __init__(self, ty, value):
346        self.__ty = ty
347        self.__value = value
348
349    def to_string(self):
350        return ""
351
352    def children(self):
353        for field in self.__ty.fields():
354            if hasattr(field, "bitpos") and field.name is not None:
355                yield (field.name, self.__value[field])
356
357
358def make_visualizer(value):
359    """Given a gdb.Value, wrap it in a pretty-printer.
360
361    If a pretty-printer is found by the usual means, it is returned.
362    Otherwise, VALUE will be wrapped in a no-op visualizer."""
363
364    result = gdb.default_visualizer(value)
365    if result is not None:
366        # Found a pretty-printer.
367        pass
368    else:
369        ty = value.type.strip_typedefs()
370        if ty.is_string_like:
371            result = NoOpScalarPrinter(value)
372        elif ty.code == gdb.TYPE_CODE_ARRAY:
373            result = NoOpArrayPrinter(ty, value)
374        elif ty.is_array_like:
375            value = value.to_array()
376            ty = value.type.strip_typedefs()
377            result = NoOpArrayPrinter(ty, value)
378        elif ty.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION):
379            result = NoOpStructPrinter(ty, value)
380        elif ty.code in (
381            gdb.TYPE_CODE_PTR,
382            gdb.TYPE_CODE_REF,
383            gdb.TYPE_CODE_RVALUE_REF,
384        ):
385            result = NoOpPointerReferencePrinter(value)
386        else:
387            result = NoOpScalarPrinter(value)
388    return result
389
390
391# Builtin pretty-printers.
392# The set is defined as empty, and files in printing/*.py add their printers
393# to this with add_builtin_pretty_printer.
394
395_builtin_pretty_printers = RegexpCollectionPrettyPrinter("builtin")
396
397register_pretty_printer(None, _builtin_pretty_printers)
398
399# Add a builtin pretty-printer.
400
401
402def add_builtin_pretty_printer(name, regexp, printer):
403    _builtin_pretty_printers.add_printer(name, regexp, printer)
404