Source code

Revision control

Copy as Markdown

Other Tools

# -*- 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"