diff options
Diffstat (limited to 'python/gdbpp/gdbpp/thashtable.py')
-rw-r--r-- | python/gdbpp/gdbpp/thashtable.py | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/python/gdbpp/gdbpp/thashtable.py b/python/gdbpp/gdbpp/thashtable.py new file mode 100644 index 0000000000..8b0294acf6 --- /dev/null +++ b/python/gdbpp/gdbpp/thashtable.py @@ -0,0 +1,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" |