summaryrefslogtreecommitdiffstats
path: root/python/gdbpp/gdbpp/thashtable.py
blob: 8b0294acf60ddb57b5e1876f7ca761be42f0cc87 (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
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import gdb

from gdbpp import GeckoPrettyPrinter


def walk_template_to_given_base(value, desired_tag_prefix):
    """Given a value of some template subclass, walk up its ancestry until we
    hit the desired type, then return the appropriate value (which will then
    have that type).
    """
    # Base case
    t = value.type
    # It's possible that we're dealing with an alias template that looks like:
    #   template<typename Protocol>
    #   using ManagedContainer = nsTHashtable<nsPtrHashKey<Protocol>>;
    # In which case we want to strip the indirection, and strip_typedefs()
    # accomplishes this.  (Disclaimer: I tried it and it worked and it didn't
    # break my other use cases, if things start exploding, do reconsider.)
    t = t.strip_typedefs()
    if t.tag.startswith(desired_tag_prefix):
        return value
    for f in t.fields():
        # we only care about the inheritance hierarchy
        if not f.is_base_class:
            continue
        # This is the answer or something we're going to need to recurse into.
        fv = value[f]
        ft = fv.type
        # slightly optimize by checking the tag rather than in the recursion
        if ft.tag.startswith(desired_tag_prefix):
            # found it!
            return fv
        return walk_template_to_given_base(fv, desired_tag_prefix)
    return None


# The templates and their inheritance hierarchy form an onion of types around
# the nsTHashtable core at the center.  All we care about is that nsTHashtable,
# but we register for the descendant types in order to avoid the default pretty
# printers having to unwrap those onion layers, wasting precious lines.
@GeckoPrettyPrinter("nsClassHashtable", "^nsClassHashtable<.*>$")
@GeckoPrettyPrinter("nsDataHashtable", "^nsDataHashtable<.*>$")
@GeckoPrettyPrinter("nsInterfaceHashtable", "^nsInterfaceHashtable<.*>$")
@GeckoPrettyPrinter("nsRefPtrHashtable", "^nsRefPtrHashtable<.*>$")
@GeckoPrettyPrinter("nsBaseHashtable", "^nsBaseHashtable<.*>$")
@GeckoPrettyPrinter("nsTHashtable", "^nsTHashtable<.*>$")
class thashtable_printer(object):
    def __init__(self, outer_value):
        self.outermost_type = outer_value.type

        value = walk_template_to_given_base(outer_value, "nsTHashtable<")
        self.value = value

        self.entry_type = value.type.template_argument(0)

        # -- Determine whether we're a hashTABLE or a hashSET
        # If we're a table, the entry type will be a nsBaseHashtableET template.
        # If we're a set, it will be something like nsPtrHashKey.
        #
        # So, assume we're a set if we're not nsBaseHashtableET<
        # (It should ideally also be true that the type ends with HashKey, but
        # since nsBaseHashtableET causes us to assume "mData" exists, let's
        # pivot based on that.)
        self.is_table = self.entry_type.tag.startswith("nsBaseHashtableET<")

        # While we know that it has a field `mKeyHash` for the hash-code and
        # book-keeping, and a DataType field mData for the value (if we're a
        # table), the key field frustratingly varies by key type.
        #
        # So we want to walk its key type to figure out the field name.  And we
        # do mean field name.  The field object is no good for subscripting the
        # value unless the field was directly owned by that value's type.  But
        # by using a string name, we save ourselves all that fanciness.

        if self.is_table:
            # For nsBaseHashtableET<KeyClass, DataType>, we want the KeyClass
            key_type = self.entry_type.template_argument(0)
        else:
            # If we're a set, our entry type is the key class already!
            key_type = self.entry_type
        self.key_field_name = None
        for f in key_type.fields():
            # No need to traverse up the type hierarchy...
            if f.is_base_class:
                continue
            # ...just to skip the fields we know exist...
            if f.name == "mKeyHash" or f.name == "mData":
                continue
            # ...and assume the first one we find is the key.
            self.key_field_name = f.name
            break

    def children(self):
        table = self.value["mTable"]

        # mEntryCount is the number of occupied slots/entries in the table.
        # We can use this to avoid doing wasted memory reads.
        entryCount = table["mEntryCount"]
        if entryCount == 0:
            return

        # The table capacity is tracked "cleverly" in terms of how many bits
        # the hash needs to be shifted.  CapacityFromHashShift calculates this
        # quantity, but may be inlined, so we replicate the calculation here.
        hashType = gdb.lookup_type("mozilla::HashNumber")
        hashBits = hashType.sizeof * 8
        capacity = 1 << (hashBits - table["mHashShift"])

        # Pierce generation-tracking EntryStore class to get at buffer.  The
        # class instance always exists, but this char* may be null.
        store = table["mEntryStore"]["mEntryStore"]

        key_field_name = self.key_field_name

        # The entry store is laid out with hashes for all possible entries
        # first, followed by all the entries.
        pHashes = store.cast(hashType.pointer())
        pEntries = pHashes + capacity
        pEntries = pEntries.cast(self.entry_type.pointer())
        seenCount = 0
        for i in range(0, int(capacity)):
            entryHash = (pHashes + i).dereference()
            # An entry hash of 0 means empty, 1 means deleted sentinel, so skip
            # if that's the case.
            if entryHash <= 1:
                continue

            entry = (pEntries + i).dereference()
            yield ("%d" % i, entry[key_field_name])
            if self.is_table:
                yield ("%d" % i, entry["mData"])

            # Stop iterating if we know there are no more occupied slots.
            seenCount += 1
            if seenCount >= entryCount:
                break

    def to_string(self):
        # The most specific template type is the most interesting.
        return str(self.outermost_type)

    def display_hint(self):
        if self.is_table:
            return "map"
        else:
            return "array"