Source code
Revision control
Copy as Markdown
Other Tools
# 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,
# Pretty-printers for SpiderMonkey's JS::Value.
import struct
import gdb
import gdb.types
import mozilla.prettyprinters
from mozilla.prettyprinters import pretty_printer
# Forget any printers from previous loads of this module.
mozilla.prettyprinters.clear_module_printers(__name__)
# See Value.h [SMDOC] JS::Value Boxing Formats for details on the JS::Value boxing
# formats handled below.
class Box(object):
def __init__(self, asBits, jtc):
self.asBits = asBits
self.jtc = jtc
# Value::asBits is uint64_t, but somehow the sign bit can be botched
# here, even though Python integers are arbitrary precision.
if self.asBits < 0:
self.asBits = self.asBits + (1 << 64)
# Return this value's type tag.
def tag(self):
raise NotImplementedError
# Return this value as a 32-bit integer, double, or address.
def as_uint32(self):
raise NotImplementedError
def as_double(self):
packed = struct.pack("q", self.asBits)
(unpacked,) = struct.unpack("d", packed)
return unpacked
def as_address(self):
raise NotImplementedError
class Punbox(Box):
# Packed non-number boxing --- the format used on x86_64. It would be nice to
# simply call Value::toInt32, etc. here, but the debugger is likely to see many
# Values, and doing several inferior calls for each one seems like a bad idea.
FULL_WIDTH = 64
TAG_SHIFT = 47
PAYLOAD_MASK = (1 << TAG_SHIFT) - 1
TAG_MASK = (1 << (FULL_WIDTH - TAG_SHIFT)) - 1
TAG_MAX_DOUBLE = 0x1FFF0
TAG_TYPE_MASK = 0x0000F
def tag(self):
tag = self.asBits >> Punbox.TAG_SHIFT
if tag <= Punbox.TAG_MAX_DOUBLE:
return self.jtc.DOUBLE
else:
return tag & Punbox.TAG_TYPE_MASK
def as_uint32(self):
return int(self.asBits & ((1 << 32) - 1))
def as_address(self):
return gdb.Value(self.asBits & Punbox.PAYLOAD_MASK)
class Nunbox(Box):
TAG_SHIFT = 32
TAG_CLEAR = 0xFFFF0000
PAYLOAD_MASK = 0xFFFFFFFF
TAG_TYPE_MASK = 0x0000000F
def tag(self):
tag = self.asBits >> Nunbox.TAG_SHIFT
if tag < Nunbox.TAG_CLEAR:
return self.jtc.DOUBLE
return tag & Nunbox.TAG_TYPE_MASK
def as_uint32(self):
return int(self.asBits & Nunbox.PAYLOAD_MASK)
def as_address(self):
return gdb.Value(self.asBits & Nunbox.PAYLOAD_MASK)
class JSValueTypeCache(object):
# Cache information about the Value type for this objfile.
def __init__(self, cache):
# Capture the tag values.
d = gdb.types.make_enum_dict(gdb.lookup_type("JSValueType"))
# The enum keys are prefixed when building with some compilers (clang at
# a minimum), so use a helper function to handle either key format.
def get(key):
val = d.get(key)
if val is not None:
return val
return d["JSValueType::" + key]
self.DOUBLE = get("JSVAL_TYPE_DOUBLE")
self.INT32 = get("JSVAL_TYPE_INT32")
self.UNDEFINED = get("JSVAL_TYPE_UNDEFINED")
self.BOOLEAN = get("JSVAL_TYPE_BOOLEAN")
self.MAGIC = get("JSVAL_TYPE_MAGIC")
self.STRING = get("JSVAL_TYPE_STRING")
self.SYMBOL = get("JSVAL_TYPE_SYMBOL")
self.BIGINT = get("JSVAL_TYPE_BIGINT")
self.NULL = get("JSVAL_TYPE_NULL")
self.OBJECT = get("JSVAL_TYPE_OBJECT")
# Let self.magic_names be an array whose i'th element is the name of
# the i'th magic value.
d = gdb.types.make_enum_dict(gdb.lookup_type("JSWhyMagic"))
self.magic_names = list(range(max(d.values()) + 1))
for k, v in d.items():
self.magic_names[v] = k
# Choose an unboxing scheme for this architecture.
self.boxer = Punbox if cache.void_ptr_t.sizeof == 8 else Nunbox
@pretty_printer("JS::Value")
class JSValue(object):
def __init__(self, value, cache):
# Save the generic typecache, and create our own, if we haven't already.
self.cache = cache
if not cache.mod_JS_Value:
cache.mod_JS_Value = JSValueTypeCache(cache)
self.jtc = cache.mod_JS_Value
self.value = value
self.box = self.jtc.boxer(value["asBits_"], self.jtc)
def to_string(self):
tag = self.box.tag()
if tag == self.jtc.UNDEFINED:
return "$JS::UndefinedValue()"
if tag == self.jtc.NULL:
return "$JS::NullValue()"
if tag == self.jtc.BOOLEAN:
return "$JS::BooleanValue(%s)" % str(self.box.as_uint32() != 0).lower()
if tag == self.jtc.MAGIC:
value = self.box.as_uint32()
if 0 <= value and value < len(self.jtc.magic_names):
return "$JS::MagicValue(%s)" % (self.jtc.magic_names[value],)
else:
return "$JS::MagicValue(%d)" % (value,)
if tag == self.jtc.INT32:
value = self.box.as_uint32()
signbit = 1 << 31
value = (value ^ signbit) - signbit
return "$JS::Int32Value(%s)" % value
if tag == self.jtc.DOUBLE:
return "$JS::DoubleValue(%s)" % self.box.as_double()
if tag == self.jtc.STRING:
value = self.box.as_address().cast(self.cache.JSString_ptr_t)
elif tag == self.jtc.OBJECT:
value = self.box.as_address().cast(self.cache.JSObject_ptr_t)
elif tag == self.jtc.SYMBOL:
value = self.box.as_address().cast(self.cache.JSSymbol_ptr_t)
elif tag == self.jtc.BIGINT:
return "$JS::BigIntValue()"
else:
value = "unrecognized!"
return "$JS::Value(%s)" % (value,)
def is_undefined(self):
return self.box.tag() == self.jtc.UNDEFINED
def get_string(self):
assert self.box.tag() == self.jtc.STRING
return self.box.as_address().cast(self.cache.JSString_ptr_t)
def get_private(self):
assert self.box.tag() == self.jtc.DOUBLE
return self.box.asBits