summaryrefslogtreecommitdiffstats
path: root/python/gdbpp/gdbpp/thashtable.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/gdbpp/gdbpp/thashtable.py')
-rw-r--r--python/gdbpp/gdbpp/thashtable.py152
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"