Bug 506717: GDB pretty-printing support for SpiderMonkey. r=sfink,ted

This commit is contained in:
Jim Blandy 2012-12-04 08:47:57 -08:00
parent 8cd6a3db7a
commit 846be8349c
42 changed files with 2310 additions and 1 deletions

View File

@ -31,7 +31,7 @@ ifneq ($(OS_ARCH),ANDROID)
TEST_DIRS += jsapi-tests
endif
TEST_DIRS += tests
TEST_DIRS += tests gdb
MODULE = js
LIBRARY_NAME = mozjs

44
js/src/gdb/Makefile.in Normal file
View File

@ -0,0 +1,44 @@
# -*- Mode: makefile -*-
#
# 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/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@ @srcdir@/tests
include $(DEPTH)/config/autoconf.mk
PROGRAM = gdb-tests$(BIN_SUFFIX)
CPPSRCS = \
gdb-tests.cpp \
test-jsid.cpp \
test-JSString.cpp \
test-JSObject.cpp \
test-jsval.cpp \
test-prettyprinters.cpp \
test-Root.cpp \
typedef-printers.cpp \
$(NULL)
# Building against js_static requires that we declare mfbt sybols "exported"
# on its behalf.
DEFINES += -DEXPORT_JS_API -DIMPL_MFBT
LIBS = $(DEPTH)/$(LIB_PREFIX)js_static.$(LIB_SUFFIX) $(NSPR_LIBS) $(MOZ_ZLIB_LIBS)
LOCAL_INCLUDES += -I$(topsrcdir) -I..
# Place a GDB Python auto-load file next to the gdb-tests executable, both
# in the build directory and in the dist/bin directory.
PP_TARGETS += GDB_AUTOLOAD
GDB_AUTOLOAD := gdb-tests-gdb.py.in
GDB_AUTOLOAD_FLAGS := -Dtopsrcdir=$(abspath $(topsrcdir))
INSTALL_TARGETS += GDB_INSTALL_AUTOLOAD
GDB_INSTALL_AUTOLOAD_FILES := $(CURDIR)/gdb-tests-gdb.py
GDB_INSTALL_AUTOLOAD_DEST := $(DIST)/bin
include $(topsrcdir)/config/rules.mk

197
js/src/gdb/README Normal file
View File

@ -0,0 +1,197 @@
This directory holds Python code to support debugging SpiderMonkey with
GDB. It includes pretty-printers for common SpiderMonkey types like jsval,
jsid, and JSObject, and makes GDB "see through" the SpiderMonkey rooting
types like js::Rooted and JS::Handle. For example:
(gdb) frame
#0 js::baseops::SetPropertyHelper (cx=0xbf3460,
obj=(JSObject * const) 0x7ffff150b060 [object global] delegate,
receiver=(JSObject * const) 0x7ffff150b060 [object global] delegate,
id=$jsid("x"), defineHow=4, vp=$jsval(1), strict=0)
at /home/jimb/moz/archer/js/src/jsobj.cpp:4495
4495 JS_ASSERT((defineHow & ~(DNP_CACHE_RESULT | DNP_UNQUALIFIED)) == 0);
(gdb)
Things to note here:
- obj, a JS::HandleObject, prints as:
obj=(JSObject * const) 0x7ffff150b060 [object global] delegate,
This immediately shows the handle's referent, along with a JavaScript-like summary
of the object.
- id, a JS::HandleId, prints as:
id=$jsid("x"),
We show the handle's referent, and print the identifier as a string.
- vp, a JS::MutableHandleValue, prints as:
vp=$jsval(1)
We show the handle's referent, using the jsval's tag to print it in its
JavaScript form.
You can still see the raw form of a value with 'print/r':
(gdb) p/r obj
$1 = {<js::HandleBase<JSObject*>> = {<No data fields>}, ptr = 0x7fffffffca60}
(gdb)
You can also use GDB's 'disable pretty-printer' command to turn off
individual pretty-printers; try 'info pretty-printer' first.
GDB should pick these extensions up automatically when you debug the shell, by
auto-loading the 'js-gdb.py' file that js/src/shell/Makefile.in places in the
same directory as the 'js' executable. You may need to add a command like the
following to your '$HOME/.gdbinit' file:
# Tell GDB to trust auto-load files found under ~/moz.
add-auto-load-safe-path ~/moz
If you do need this, GDB will tell you.
In general, pretty-printers for pointer types include a summary of the
pointer's referent:
(gdb) b math_atan2
Breakpoint 1 at 0x542e0a: file /home/jimb/moz/archer/js/src/jsmath.cpp, line 214.
(gdb) run
js> Math.atan2('Spleen', 42)
Breakpoint 1, math_atan2 (cx=0xbf3440, argc=2, vp=0x7ffff172f0a0)
(gdb) print vp[0]
$1 = $jsval((JSObject *) 0x7ffff151c0c0 [object Function "atan2"])
(gdb) print vp[1]
$2 = $jsval((JSObject *) 0x7ffff150d0a0 [object Math])
(gdb) print vp[2]
$3 = $jsval("Spleen")
(gdb) print vp[3]
$4 = $jsval(42)
(gdb)
We used to also have pretty-printers for the actual contents of a JSString
struct, that knew which union branches were live and which were dead. These were
more fragile than the summary pretty-printers, and harder to test, so I've
removed them until we can see how to do better.
There are unit tests; see 'Running the unit tests', below.
I'd love for others to pitch in. GDB's Python API is documented in the GDB
manual.
I've recently rewritten the printers. The new code is simpler, and more
robust; unit tests are easier to write; and the new test harness can run
the tests in parallel. If a printer you'd contributed to in the past was
dropped in the process, I apologize; I felt we should have good test
coverage for any printer landed in-tree. You may also be interested in
'Personal pretty-printers', below.
Directory layout
----------------
- js/src/gdb/mozilla: The actual SpiderMonkey support code. GDB auto-loads this
when you debug an executable or shared library that contains SpiderMonkey.
- js/src/gdb/tests: Unit tests for the above.
- Each '.py' file is a unit test, to be run by js/src/gdb/run-tests.py.
- Each '.cpp' file contains C++ code fragments for some unit test to use.
- js/src/gdb/lib-for-tests: Python modules used by the unit tests.
In js/src/gdb:
- run-tests.py: test harness for GDB SpiderMonkey support unit tests. See
'Running the unit tests', below.
- taskpool.py, progressbar.py: Python modules used by run-tests.py.
- gdb-tests.cpp, gdb-tests.h: Driver program for C++ code fragments.
- gdb-tests-gdb.py.in: Template for GDB autoload file for gdb-tests.
Personal pretty-printers
------------------------
If you'd like to write your own pretty-printers, you can put them in a
module named 'my_mozilla_printers' in a directory somewhere on your Python
module search path. Our autoload code tries to import 'my_mozilla_printers'
after importing our other SpiderMonkey support modules. For example:
$ echo $PYTHONPATH
/home/jimb/python
$ cat ~/python/my_mozilla_printers.py
import gdb
from mozilla.prettyprinters import ptr_pretty_printer
# Simple jschar * printer. Doesn't show address; chases null pointers.
@ptr_pretty_printer('jschar')
class jscharPtr(object):
def __init__(self, value, cache): self.value = value
def display_hint(self): return 'string'
def to_string(self):
c = u''
for i in xrange(50):
if self.value[i] == 0: break
c += unichr(self.value[i])
return c
$
...
(gdb) whatis sample
type = jschar [4]
(gdb) print &sample[0]
$1 = "Hi!"
Running the unit tests
----------------------
These extensions have unit tests, invoked as follows:
$ python run-tests.py [OPTIONS] LIBDIR [TESTS...]
where LIBDIR is a directory containing a compiled SpiderMonkey library,
libmozjs.so; TESTS are names of selected tests to run (if omitted, we run
them all); and OPTIONS are drawn from the list below.
--gdb=EXECUTABLE
Instead of running whatever 'gdb' we find in our search path, use
EXECUTABLE to run the tests.
--srcdir=SRCDIR
Find the sources corresponding to LIBDIR/libmozjs.so in SRCDIR. Without
this option, we use the parent of the directory containing
'run-tests.py'. Note that SRCDIR must be a complete SpiderMonkey source
directory, as our tests #include internal SpiderMonkey header files (to
test pretty-printers for internal types, like parse nodes.)
--testdir=TESTDIR
Search for Python scripts and any accompanying C++ source code in
TESTDIR. If omitted, we use the 'tests' directory in the directory
containing 'run-tests.py'.
--builddir=BUILDDIR
Build the C++ executable that GDB debugs to run the tests in BUILDDIR.
If omitted, create a 'gdb-tests' subdirectory of LIBDIR.
(If a path like LIBDIR or SRCDIR is relative, it is interpreted relative to
the directory that was current when run-tests.py was executed, regardless
of whatever changes to the current directory the test harness makes.)
For example, since I build in a subdirectory 'obj~' of the js/src directory, I
use this command from js/src to run the pretty-printer unit tests:
$ python gdb/run-tests.py --libdir=obj~
Writing new unit tests
----------------------
Each unit test consists of a Python script, possibly with some accompanying
C++ code. Running tests works like this:
- The run-tests.py script calls 'make' to compile all the '.cpp' files in
js/src/gdb/tests, and then links them with js/src/gdb/gdb-tests.cpp and
LIBDIR/libmozjs.so, producing a single executable file.
- Then, for each '.py' test script in js/src/gdb/tests, the harness starts
GDB on the executable, and has it run js/src/gdb/lib-for-tests/prolog.py,
and then the given test script.
Thanks To:
----------
- David Anderson
- Steve Fink
- Chris Leary
- Josh Matthews
- Jason Orendorff
- Andrew Sutherland

22
js/src/gdb/TODO Normal file
View File

@ -0,0 +1,22 @@
* Ideas:
- jschar *
- js::Shape, js::Baseshape
- printers for structures with horrible unions (JSString, JSParseNode)
- bring back parse_node.py
- New 'js show' command for showing full trees, property lists, hash table
contents, and so on --- JSParseNode * should not show the whole tree.
Possibly clean up some "pointer-only" stuff in parse_node.py.
- 'js show <defn>' lists a JSDefinition's uses
- 'js show <parsenode>' shows entire tree
- 'js show <scope>' lists all properties (parents)
- 'js tree <scope>' shows property tree
- avoid dead union branches in js::Shape; print attrs nicely
- Print JSScope with identifier.
- Print JSAtomSets, and thus PN_NAMESET.
- JSParseNode PN_NAMESET
- 'JSClass *' pretty-printer
Local variables:
mode: org
End:

View File

@ -0,0 +1,8 @@
"""GDB Python customization auto-loader for GDB test executable"""
#filter substitution
import os.path
sys.path[0:0] = [os.path.join('@topsrcdir@', 'gdb')]
import mozilla.autoload
mozilla.autoload.register(gdb.current_objfile())

91
js/src/gdb/gdb-tests.cpp Normal file
View File

@ -0,0 +1,91 @@
/* 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/. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gdb-tests.h"
using namespace JS;
/* The class of the global object. */
JSClass global_class = {
"global", JSCLASS_GLOBAL_FLAGS,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
};
template<typename T>
inline T *
checkPtr(T *ptr)
{
if (! ptr)
abort();
return ptr;
}
void
checkBool(JSBool success)
{
if (! success)
abort();
}
/* The error reporter callback. */
void reportError(JSContext *cx, const char *message, JSErrorReport *report)
{
fprintf(stderr, "%s:%u: %s\n",
report->filename ? report->filename : "<no filename>",
(unsigned int) report->lineno,
message);
}
// prolog.py sets a breakpoint on this function; test functions can call it
// to easily return control to GDB where desired.
void breakpoint() {}
GDBFragment *GDBFragment::allFragments = NULL;
int
main (int argc, const char **argv)
{
JSRuntime *runtime = checkPtr(JS_NewRuntime(1024 * 1024, JS_USE_HELPER_THREADS));
JS_SetGCParameter(runtime, JSGC_MAX_BYTES, 0xffffffff);
JS_SetNativeStackQuota(runtime, 5000000);
JSContext *cx = checkPtr(JS_NewContext(runtime, 8192));
JS_SetVersion(cx, JSVERSION_LATEST);
JS_SetErrorReporter(cx, reportError);
JSAutoRequest ar(cx);
/* Create the global object. */
js::RootedObject global(cx, checkPtr(JS_NewGlobalObject(cx, &global_class, NULL)));
JS_SetGlobalObject(cx, global);
JSAutoCompartment ac(cx, global);
/* Populate the global object with the standard globals,
like Object and Array. */
checkBool(JS_InitStandardClasses(cx, global));
argv++;
while (*argv) {
const char *name = *argv++;
GDBFragment *fragment;
for (fragment = GDBFragment::allFragments; fragment; fragment = fragment->next) {
if (strcmp(fragment->name(), name) == 0) {
fragment->run(cx, argv);
break;
}
}
if (!fragment) {
fprintf(stderr, "Unrecognized fragment name: %s\n", name);
exit(1);
}
}
return 0;
}

70
js/src/gdb/gdb-tests.h Normal file
View File

@ -0,0 +1,70 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=99:
*
* 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/. */
// Support for C++ fragments to be used by Python unit tests for SpiderMonkey's
// GDB support.
//
// That is:
// - js/src/gdb/mozilla holds the actual GDB SpiderMonkey support code.
// - Each '.py' file in js/src/gdb/tests is a unit test for the above.
// - Each '.cpp' file in js/src/gdb/tests is C++ code for one of the unit tests
// to run.
//
// (So the .cpp files are two steps removed from being anything one would
// actually run.)
#include "jsapi.h"
void breakpoint();
struct GDBFragment {
GDBFragment() {
next = allFragments;
allFragments = this;
}
// The name of this fragment. gdb-tests.cpp runs the fragments whose names
// are passed to it on the command line.
virtual const char *name() = 0;
// Run the fragment code. |argv| is a reference to the pointer into the
// command-line argument vector, referring to the argument immediately
// following this fragment's name. The fragment can consume arguments and
// advance argv if it wishes.
virtual void run(JSContext *cx, const char **&argv) = 0;
// We declare one instance of this type for each fragment to run. The
// constructor adds each instance to a linked list, of which this is
// the head.
static GDBFragment *allFragments;
// The link in the list of all instances.
GDBFragment *next;
};
// Macro for declaring a C++ fragment for some Python unit test to call. Usage:
//
// FRAGMENT(<category>, <name>) { <body of fragment function> }
//
// where <category> and <name> are identifiers. The gdb-tests executable
// takes a series of fragment names as command-line arguments and runs them in
// turn; each fragment is named <category>.<name> on the command line.
//
// The body runs in a scope where 'cx' is a usable JSContext *.
#define FRAGMENT(category, subname) \
class FRAGMENT_CLASS_NAME(category, subname): public GDBFragment { \
void run(JSContext *cx, const char **&argv); \
const char *name() { return FRAGMENT_STRING_NAME(category, subname); } \
static FRAGMENT_CLASS_NAME(category, subname) singleton; \
}; \
FRAGMENT_CLASS_NAME(category, subname) FRAGMENT_CLASS_NAME(category, subname)::singleton; \
void FRAGMENT_CLASS_NAME(category, subname)::run(JSContext *cx, const char **&argv)
#define FRAGMENT_STRING_NAME(category, subname) (#category "." #subname)
#define FRAGMENT_CLASS_NAME(category, subname) Fragment_ ## category ## _ ## subname

View File

@ -0,0 +1,19 @@
# Apparently, there's simply no way to ask GDB to exit with a non-zero
# status when the script run with the --python option fails. Thus, if we
# have --python run prolog.py directly, syntax errors there will lead GDB
# to exit with no indication anything went wrong.
#
# To avert that, we use this very small launcher script to run prolog.py
# and catch errors.
#
# Remember, errors in this file will cause spurious passes, so keep this as
# simple as possible!
import sys
import traceback
try:
execfile(sys.argv.pop(0))
except Exception as err:
sys.stderr.write('Error running GDB prologue:\n')
traceback.print_exc()
sys.exit(1)

View File

@ -0,0 +1,69 @@
import gdb
import re
import sys
import traceback
# Run the C++ fragment named |fragment|, stopping on entry to |function|
# ('breakpoint', by default) and then select the calling frame.
def run_fragment(fragment, function='breakpoint'):
# Arrange to stop at a reasonable place in the test program.
bp = gdb.Breakpoint(function);
try:
gdb.execute("run %s" % (fragment,))
# Check that we did indeed stop by hitting the breakpoint we set.
assert bp.hit_count == 1
finally:
bp.delete()
gdb.execute('frame 1')
# Assert that |actual| is equal to |expected|; if not, complain in a helpful way.
def assert_eq(actual, expected):
if actual != expected:
raise AssertionError, """Unexpected result:
expected: %r
actual: %r""" % (expected, actual)
# Assert that |value|'s pretty-printed form is |form|. If |value| is a
# string, then evaluate it with gdb.parse_and_eval to produce a value.
def assert_pretty(value, form):
if isinstance(value, str):
value = gdb.parse_and_eval(value)
assert_eq(str(value), form)
# Check that the list of registered pretty-printers includes one named
# |printer|, with a subprinter named |subprinter|.
def assert_subprinter_registered(printer, subprinter):
# Match a line containing |printer| followed by a colon, and then a
# series of more-indented lines containing |subprinter|.
names = { 'printer': re.escape(printer), 'subprinter': re.escape(subprinter) }
pat = r'^( +)%(printer)s *\n(\1 +.*\n)*\1 +%(subprinter)s *\n' % names
output = gdb.execute('info pretty-printer', to_string=True)
if not re.search(pat, output, re.MULTILINE):
raise AssertionError, ("assert_subprinter_registered failed to find pretty-printer:\n"
" %s:%s\n"
"'info pretty-printer' says:\n"
"%s" % (printer, subprinter, output))
# Request full stack traces for Python errors.
gdb.execute('set python print-stack full')
# Tell GDB not to ask the user about the things we tell it to do.
gdb.execute('set confirm off', False)
# Some print settings that make testing easier.
gdb.execute('set print static-members off')
gdb.execute('set print address off')
gdb.execute('set print pretty off')
gdb.execute('set width 0')
try:
execfile(sys.argv[0])
except AssertionError as err:
sys.stderr.write('\nAssertion traceback:\n')
(t, v, tb) = sys.exc_info()
traceback.print_tb(tb)
sys.stderr.write('\nTest assertion failed:\n')
sys.stderr.write(str(err))
sys.exit(1)

View File

@ -0,0 +1,42 @@
# Pretty-printers for SpiderMonkey JSObjects.
import gdb
import mozilla.JSString
import mozilla.prettyprinters
from mozilla.prettyprinters import ptr_pretty_printer
from mozilla.Root import deref
mozilla.prettyprinters.clear_module_printers(__name__)
class JSObjectTypeCache(object):
def __init__(self, value, cache):
baseshape_flags = gdb.lookup_type('js::BaseShape::Flag')
self.flag_DELEGATE = baseshape_flags['js::BaseShape::DELEGATE'].bitpos
self.func_ptr_type = gdb.lookup_type('JSFunction').pointer()
# There should be no need to register this for JSFunction as well, since we
# search for pretty-printers under the names of base classes, and
# JSFunction has JSObject as a base class.
@ptr_pretty_printer('JSObject')
class JSObjectPtr(mozilla.prettyprinters.Pointer):
def __init__(self, value, cache):
super(JSObjectPtr, self).__init__(value, cache)
if not cache.mod_JSObject:
cache.mod_JSObject = JSObjectTypeCache(value, cache)
self.otc = cache.mod_JSObject
def summary(self):
shape = deref(self.value['shape_'])
baseshape = deref(shape['base_'])
class_name = baseshape['clasp']['name'].string()
flags = baseshape['flags']
is_delegate = bool(flags & self.otc.flag_DELEGATE)
name = None
if class_name == 'Function':
function = self.value.cast(self.otc.func_ptr_type)
atom = deref(function['atom_'])
name = str(atom) if atom else '<unnamed>'
return '[object %s%s]%s' % (class_name,
' ' + name if name else '',
' delegate' if is_delegate else '')

View File

@ -0,0 +1,55 @@
# Pretty-printers for SpiderMonkey strings.
import gdb
import mozilla.prettyprinters
from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer
# Forget any printers from previous loads of this module.
mozilla.prettyprinters.clear_module_printers(__name__)
# Cache information about the JSString type for this objfile.
class JSStringTypeCache(object):
def __init__(self, cache):
dummy = gdb.Value(0).cast(cache.JSString_ptr_t)
self.LENGTH_SHIFT = dummy['LENGTH_SHIFT']
self.FLAGS_MASK = dummy['FLAGS_MASK']
self.ROPE_FLAGS = dummy['ROPE_FLAGS']
self.ATOM_BIT = dummy['ATOM_BIT']
class Common(mozilla.prettyprinters.Pointer):
def __init__(self, value, cache):
super(Common, self).__init__(value, cache)
if not cache.mod_JSString:
cache.mod_JSString = JSStringTypeCache(cache)
self.stc = cache.mod_JSString
@ptr_pretty_printer("JSString")
class JSStringPtr(Common):
def display_hint(self):
return "string"
def jschars(self):
d = self.value['d']
lengthAndFlags = d['lengthAndFlags']
length = lengthAndFlags >> self.stc.LENGTH_SHIFT
is_rope = (lengthAndFlags & self.stc.FLAGS_MASK) == self.stc.ROPE_FLAGS
if is_rope:
for c in JSStringPtr(d['u1']['left'], self.cache).jschars():
yield c
for c in JSStringPtr(d['s']['u2']['right'], self.cache).jschars():
yield c
else:
chars = d['u1']['chars']
for i in xrange(length):
yield chars[i]
def to_string(self):
s = u''
for c in self.jschars():
s += unichr(c)
return s
@ptr_pretty_printer("JSAtom")
class JSAtomPtr(Common):
def to_string(self):
return self.value.cast(self.cache.JSString_ptr_t)

View File

@ -0,0 +1,63 @@
# Pretty-printers and utilities for SpiderMonkey rooting templates:
# Rooted, Handle, MutableHandle, etc.
import mozilla.prettyprinters
from mozilla.prettyprinters import pretty_printer, template_pretty_printer
# Forget any printers from previous loads of this module.
mozilla.prettyprinters.clear_module_printers(__name__)
# Common base class for all the rooting template pretty-printers. All these
# templates have one member holding the referent (or a pointer to it), so
# there's not much to it.
class Common(object):
# The name of the template member holding the referent.
member = 'ptr'
# If True, this is a handle type, and should be dereferenced. If False,
# the template member holds the referent directly.
handle = False
def __init__(self, value, cache):
self.value = value
def to_string(self):
ptr = self.value[self.member]
if self.handle:
ptr = ptr.dereference()
# As of 2012-11, GDB suppresses printing pointers in replacement values;
# see http://sourceware.org/ml/gdb/2012-11/msg00055.html That means that
# simply returning the 'ptr' member won't work. Instead, just invoke
# GDB's formatter ourselves.
return str(ptr)
@template_pretty_printer("js::Rooted")
class Rooted(Common):
pass
@template_pretty_printer("JS::Handle")
class Handle(Common):
handle = True
@template_pretty_printer("JS::MutableHandle")
class MutableHandle(Common):
handle = True
@template_pretty_printer("js::EncapsulatedPtr")
class EncapsulatedPtr(Common):
member = 'value'
@pretty_printer("js::EncapsulatedValue")
class EncapsulatedValue(Common):
member = 'value'
# Return the referent of a HeapPtr, Rooted, or Handle.
def deref(root):
tag = root.type.strip_typedefs().tag
if tag.startswith('js::HeapPtr<'):
return root['value']
elif tag.startswith('js::Rooted<'):
return root['ptr']
elif tag.startswith('js::Handle<'):
return root['ptr']
else:
raise NotImplementedError

View File

@ -0,0 +1 @@
# Yes, Python, this is a package.

View File

@ -0,0 +1,24 @@
# mozilla/autoload.py: Autoload SpiderMonkey pretty-printers.
import gdb.printing
import mozilla.prettyprinters
# Import the pretty-printer modules. As a side effect, loading these
# modules registers their printers with mozilla.prettyprinters.
import mozilla.jsid
import mozilla.JSObject
import mozilla.JSString
import mozilla.jsval
import mozilla.Root
# The user may have personal pretty-printers. Get those, too, if they exist.
try:
import my_mozilla_printers
except ImportError:
pass
# Register our pretty-printers with |objfile|.
def register(objfile):
lookup = mozilla.prettyprinters.lookup_for_objfile(objfile)
if lookup:
gdb.printing.register_pretty_printer(objfile, lookup, replace=True)

View File

@ -0,0 +1,56 @@
# Pretty-printers for JSID values.
import gdb
import mozilla.prettyprinters
from mozilla.prettyprinters import pretty_printer
# Forget any printers from previous loads of this module.
mozilla.prettyprinters.clear_module_printers(__name__)
@pretty_printer('jsid')
class jsid(object):
# Since people don't always build with macro debugging info, I can't
# think of any way to avoid copying these values here, short of using
# inferior calls for every operation (which, I hear, is broken from
# pretty-printers in some recent GDBs).
TYPE_STRING = 0x0
TYPE_INT = 0x1
TYPE_VOID = 0x2
TYPE_OBJECT = 0x4
TYPE_DEFAULT_XML_NAMESPACE = 0x6
TYPE_MASK = 0x7
def __init__(self, value, cache):
self.value = value
self.cache = cache
# SpiderMonkey has two alternative definitions of jsid: a typedef for
# ptrdiff_t, and a struct with == and != operators defined on it.
# Extract the bits from either one.
def as_bits(self):
if self.value.type.code == gdb.TYPE_CODE_STRUCT:
return self.value['asBits']
elif self.value.type.code == gdb.TYPE_CODE_INT:
return self.value
else:
raise RuntimeError, ("definition of SpiderMonkey 'jsid' type"
"neither struct nor integral type")
def to_string(self):
bits = self.as_bits()
tag = bits & jsid.TYPE_MASK
if tag == jsid.TYPE_STRING:
body = bits.cast(self.cache.JSString_ptr_t)
elif tag & jsid.TYPE_INT:
body = bits >> 1
elif tag == jsid.TYPE_VOID:
return "JSID_VOID"
elif tag == jsid.TYPE_OBJECT:
body = ((bits & ~jsid.TYPE_MASK)
.cast(self.cache.JSObject_ptr_t))
elif tag == jsid.TYPE_DEFAULT_XML_NAMESPACE:
return "JS_DEFAULT_XML_NAMESPACE_ID"
else:
body = "<unrecognized>"
return '$jsid(%s)' % (body,)

219
js/src/gdb/mozilla/jsval.py Normal file
View File

@ -0,0 +1,219 @@
# Pretty-printers for SpiderMonkey jsvals.
import gdb
import gdb.types
import mozilla.prettyprinters
from mozilla.prettyprinters import pretty_printer, ptr_pretty_printer
# Forget any printers from previous loads of this module.
mozilla.prettyprinters.clear_module_printers(__name__)
# Summary of the JS::Value (also known as jsval) type:
#
# Viewed abstractly, JS::Value is a 64-bit discriminated union, with
# JSString *, JSObject *, IEEE 64-bit floating-point, and 32-bit integer
# branches (and a few others). (It is not actually a C++ union;
# 'discriminated union' just describes the overall effect.) Note that
# JS::Value is always 64 bits long, even on 32-bit architectures.
#
# The ECMAScript standard specifies that ECMAScript numbers are IEEE 64-bit
# floating-point values. A JS::Value can represent any JavaScript number
# value directly, without referring to additional storage, or represent an
# object, string, or other ECMAScript value, and remember which type it is.
# This may seem surprising: how can a 64-bit type hold all the 64-bit IEEE
# values, and still distinguish them from objects, strings, and so on,
# which have 64-bit addresses?
#
# This is possible for two reasons:
#
# - First, ECMAScript implementations aren't required to distinguish all
# the values the IEEE 64-bit format can represent. The IEEE format
# specifies many bitstrings representing NaN values, while ECMAScript
# requires only a single NaN value. This means we can use one IEEE NaN to
# represent ECMAScript's NaN, and use all the other IEEE NaNs to
# represent the other ECMAScript values.
#
# (IEEE says that any floating-point value whose 11-bit exponent field is
# 0x7ff (all ones) and whose 52-bit fraction field is non-zero is a NaN.
# So as long as we ensure the fraction field is non-zero, and save a NaN
# for ECMAScript, we have 2^52 values to play with.)
#
# - Second, on the only 64-bit architecture we support, x86_64, only the
# lower 48 bits of an address are significant. The upper sixteen bits are
# required to be the sign-extension of bit 48. Furthermore, user code
# always runs in "positive addresses": those in which bit 48 is zero. So
# we only actually need 47 bits to store all possible object or string
# addresses, even on 64-bit platforms.
#
# With a 52-bit fraction field, and 47 bits needed for the 'payload', we
# have up to five bits left to store a 'tag' value, to indicate which
# branch of our discriminated union is live.
#
# Thus, we define JS::Value representations in terms of the IEEE 64-bit
# floating-point format:
#
# - Any bitstring that IEEE calls a number or an infinity represents that
# ECMAScript number.
#
# - Any bitstring that IEEE calls a NaN represents either an ECMAScript NaN
# or a non-number ECMAScript value, as determined by a tag field stored
# towards the most significant end of the fraction field (exactly where
# depends on the address size). If the tag field indicates that this
# JS::Value is an object, the fraction field's least significant end
# holds the address of a JSObject; if a string, the address of a
# JSString; and so on.
#
# On the only 64-bit platform we support, x86_64, only the lower 48 bits of
# an address are significant, and only those values whose top bit is zero
# are used for user-space addresses. This means that x86_64 addresses are
# effectively 47 bits long, and thus fit nicely in the available portion of
# the fraction field.
#
#
# In detail:
#
# - jsval (jsapi.h) is a typedef for JS::Value.
#
# - JS::Value (jsapi.h) is a class with a lot of methods and a single data
# member, of type jsval_layout.
#
# - jsval_layout (jsval.h) is a helper type for picking apart values. This
# is always 64 bits long, with a variant for each address size (32 bits
# or 64 bits) and endianness (little- or big-endian).
#
# jsval_layout is a union with 'asBits', 'asDouble', and 'asPtr'
# branches, and an 's' branch, which is a struct that tries to break out
# the bitfields a little for the non-double types. On 64-bit machines,
# jsval_layout also has an 'asUIntPtr' branch.
#
# On 32-bit platforms, the 's' structure has a 'tag' member at the
# exponent end of the 's' struct, and a 'payload' union at the mantissa
# end. The 'payload' union's branches are things like JSString *,
# JSObject *, and so on: the natural representations of the tags.
#
# On 64-bit platforms, the payload is 47 bits long; since C++ doesn't let
# us declare bitfields that hold unions, we can't break it down so
# neatly. In this case, we apply bit-shifting tricks to the 'asBits'
# branch of the union to extract the tag.
class Box(object):
def __init__(self, asBits, jtc):
self.asBits = asBits
self.jtc = jtc
# jsval_layout::asBits is uint64, but somebody botches the sign bit, 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): raise NotImplementedError
def as_address(self): raise NotImplementedError
# Packed non-number boxing --- the format used on x86_64. It would be nice to simply
# call JSVAL_TO_INT, etc. here, but the debugger is likely to see many jsvals, and
# doing several inferior calls for each one seems like a bad idea.
class Punbox(Box):
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)
# Cache information about the jsval type for this objfile.
class jsvalTypeCache(object):
def __init__(self, cache):
# Capture the tag values.
d = gdb.types.make_enum_dict(gdb.lookup_type('JSValueType'))
self.DOUBLE = d['JSVAL_TYPE_DOUBLE']
self.INT32 = d['JSVAL_TYPE_INT32']
self.UNDEFINED = d['JSVAL_TYPE_UNDEFINED']
self.BOOLEAN = d['JSVAL_TYPE_BOOLEAN']
self.MAGIC = d['JSVAL_TYPE_MAGIC']
self.STRING = d['JSVAL_TYPE_STRING']
self.NULL = d['JSVAL_TYPE_NULL']
self.OBJECT = d['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 = range(max(d.itervalues()) + 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('jsval_layout')
class jsval_layout(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_jsval:
cache.mod_jsval = jsvalTypeCache(cache)
self.jtc = cache.mod_jsval
self.value = value
self.box = self.jtc.boxer(value['asBits'], self.jtc)
def to_string(self):
tag = self.box.tag()
if tag == self.jtc.INT32:
value = self.box.as_uint32()
signbit = 1 << 31
value = (value ^ signbit) - signbit
elif tag == self.jtc.UNDEFINED:
return 'JSVAL_VOID'
elif tag == self.jtc.BOOLEAN:
return 'JSVAL_TRUE' if self.box.as_uint32() else 'JSVAL_FALSE'
elif tag == self.jtc.MAGIC:
value = self.box.as_uint32()
if 0 <= value and value < len(self.jtc.magic_names):
return '$jsmagic(%s)' % (self.jtc.magic_names[value],)
else:
return '$jsmagic(%d)' % (value,)
elif tag == self.jtc.STRING:
value = self.box.as_address().cast(self.cache.JSString_ptr_t)
elif tag == self.jtc.NULL:
return 'JSVAL_NULL'
elif tag == self.jtc.OBJECT:
value = self.box.as_address().cast(self.cache.JSObject_ptr_t)
elif tag == self.jtc.DOUBLE:
value = self.value['asDouble']
else:
return '$jsval(unrecognized!)'
return '$jsval(%s)' % (value,)
@pretty_printer('JS::Value')
class JSValue(object):
def __new__(cls, value, cache):
return jsval_layout(value['data'], cache)

View File

@ -0,0 +1,313 @@
# mozilla/prettyprinters.py --- infrastructure for SpiderMonkey's auto-loaded pretty-printers.
import gdb
import re
# Decorators for declaring pretty-printers.
#
# In each case, the decoratee should be a SpiderMonkey-style pretty-printer
# factory, taking both a gdb.Value instance and a TypeCache instance as
# arguments; see TypeCache, below.
# Check that |fn| hasn't been registered as a pretty-printer under some
# other name already. (The 'enabled' flags used by GDB's
# 'enable/disable/info pretty-printer' commands are simply stored as
# properties of the function objects themselves, so a single function
# object can't carry the 'enabled' flags for two different printers.)
def check_for_reused_pretty_printer(fn):
if hasattr(fn, 'enabled'):
raise RuntimeError, ("pretty-printer function %r registered more than once" % fn)
# a dictionary mapping gdb.Type tags to pretty-printer functions.
printers_by_tag = {}
# A decorator: add the decoratee as a pretty-printer lookup function for types
# named |type_name|.
def pretty_printer(type_name):
def add(fn):
check_for_reused_pretty_printer(fn)
add_to_subprinter_list(fn, type_name)
printers_by_tag[type_name] = fn
return fn
return add
# a dictionary mapping gdb.Type tags to pretty-printer functions for pointers to
# that type.
ptr_printers_by_tag = {}
# A decorator: add the decoratee as a pretty-printer lookup function for
# pointers to types named |type_name|.
def ptr_pretty_printer(type_name):
def add(fn):
check_for_reused_pretty_printer(fn)
add_to_subprinter_list(fn, "ptr-to-" + type_name)
ptr_printers_by_tag[type_name] = fn
return fn
return add
# a dictionary mapping the template name portion of gdb.Type tags to
# pretty-printer functions for instantiations of that template.
template_printers_by_tag = {}
# A decorator: add the decoratee as a pretty-printer lookup function for
# instantiations of templates named |template_name|.
def template_pretty_printer(template_name):
def add(fn):
check_for_reused_pretty_printer(fn)
add_to_subprinter_list(fn, 'instantiations-of-' + template_name)
template_printers_by_tag[template_name] = fn
return fn
return add
# A list of (REGEXP, PRINTER) pairs, such that if REGEXP (a RegexObject)
# matches the result of converting a gdb.Value's type to a string, then
# PRINTER is a pretty-printer lookup function that will probably like that
# value.
printers_by_regexp = []
# A decorator: add the decoratee as a pretty-printer factory for types
# that, when converted to a string, match |pattern|. Use |name| as the
# pretty-printer's name, when listing, enabling and disabling.
def pretty_printer_for_regexp(pattern, name):
compiled = re.compile(pattern)
def add(fn):
check_for_reused_pretty_printer(fn)
add_to_subprinter_list(fn, name)
printers_by_regexp.append((compiled, fn))
return fn
return add
# Forget all pretty-printer lookup functions defined in the module name
# |module_name|, if any exist. Use this at the top of each pretty-printer
# module like this:
#
# clear_module_printers(__name__)
def clear_module_printers(module_name):
global printers_by_tag, ptr_printers_by_tag, template_printers_by_tag, printers_by_regexp
# Remove all pretty-printers defined in the module named |module_name|
# from d.
def clear_dictionary(d):
# Walk the dictionary, building a list of keys whose entries we
# should remove. (It's not safe to delete entries from a dictionary
# while we're iterating over it.)
to_delete = []
for (k, v) in d.iteritems():
if v.__module__ == module_name:
to_delete.append(k)
remove_from_subprinter_list(v)
for k in to_delete:
del d[k]
clear_dictionary(printers_by_tag)
clear_dictionary(ptr_printers_by_tag)
clear_dictionary(template_printers_by_tag)
# Iterate over printers_by_regexp, deleting entries from the given module.
new_list = []
for p in printers_by_regexp:
if p.__module__ == module_name:
remove_from_subprinter_list(p)
else:
new_list.append(p)
printers_by_regexp = new_list
# Our subprinters array. The 'subprinters' attributes of all lookup
# functions returned by lookup_for_objfile point to this array instance,
# which we mutate as subprinters are added and removed.
subprinters = []
# Set up the 'name' and 'enabled' attributes on |subprinter|, and add it to our
# list of all SpiderMonkey subprinters.
def add_to_subprinter_list(subprinter, name):
subprinter.name = name
subprinter.enabled = True
subprinters.append(subprinter)
# Remove |subprinter| from our list of all SpiderMonkey subprinters.
def remove_from_subprinter_list(subprinter):
subprinters.remove(subprinter)
# An exception class meaning, "This objfile has no SpiderMonkey in it."
class NotSpiderMonkeyObjfileError(TypeError):
pass
# TypeCache: a cache for frequently used information about an objfile.
#
# When a new SpiderMonkey objfile is loaded, we construct an instance of
# this class for it. Then, whenever we construct a pretty-printer for some
# gdb.Value, we also pass, as a second argument, the TypeCache for the
# objfile to which that value's type belongs.
#
# if objfile doesn't seem to have SpiderMonkey code in it, the constructor
# raises NotSpiderMonkeyObjfileError.
#
# Pretty-printer modules may add attributes to this to hold their own
# cached values. Such attributes should be named mod_NAME, where the module
# is named mozilla.NAME; for example, mozilla.JSString should store its
# metadata in the TypeCache's mod_JSString attribute.
class TypeCache(object):
def __init__(self, objfile):
self.objfile = objfile
# Unfortunately, the Python interface doesn't allow us to specify
# the objfile in whose scope lookups should occur. But simply
# knowing that we need to lookup the types afresh is probably
# enough.
self.void_t = gdb.lookup_type('void')
self.void_ptr_t = self.void_t.pointer()
try:
self.JSString_ptr_t = gdb.lookup_type('JSString').pointer()
self.JSObject_ptr_t = gdb.lookup_type('JSObject').pointer()
except gdb.error:
raise NotSpiderMonkeyObjfileError
self.mod_JSString = None
self.mod_JSObject = None
self.mod_jsval = None
# Yield a series of all the types that |t| implements, by following typedefs
# and iterating over base classes. Specifically:
# - |t| itself is the first value yielded.
# - If we yield a typedef, we later yield its definition.
# - If we yield a type with base classes, we later yield those base classes.
# - If we yield a type with some base classes that are typedefs,
# we yield all the type's base classes before following the typedefs.
# (Actually, this never happens, because G++ doesn't preserve the typedefs in
# the DWARF.)
#
# This is a hokey attempt to order the implemented types by meaningfulness when
# pretty-printed. Perhaps it is entirely misguided, and we should actually
# collect all applicable pretty-printers, and then use some ordering on the
# pretty-printers themselves.
#
# We may yield a type more than once (say, if it appears more than once in the
# class hierarchy).
def implemented_types(t):
# Yield all types that follow |t|.
def followers(t):
if t.code == gdb.TYPE_CODE_TYPEDEF:
yield t.target()
for t2 in followers(t.target()): yield t2
elif t.code == gdb.TYPE_CODE_STRUCT:
base_classes = []
for f in t.fields():
if f.is_base_class:
yield f.type
base_classes.append(f.type)
for b in base_classes:
for t2 in followers(b): yield t2
yield t
for t2 in followers(t): yield t2
template_regexp = re.compile("([\w_:]+)<")
# Make a lookup function for objfile.
def lookup_for_objfile(objfile):
# Create a type cache for this objfile.
try:
cache = TypeCache(objfile)
except NotSpiderMonkeyObjfileError:
if gdb.parameter("verbose"):
gdb.write("objfile '%s' has no SpiderMonkey code; not registering pretty-printers\n"
% (objfile.filename,))
# Return a pretty-printer for |value|, if we have one. This is the lookup
# function object we place in each gdb.Objfile's pretty-printers list, so it
# carries |name|, |enabled|, and |subprinters| attributes.
def lookup(value):
# If |table| has a pretty-printer for |tag|, apply it to |value|.
def check_table(table, tag):
if tag in table:
f = table[tag]
if f.enabled:
return f(value, cache)
return None
for t in implemented_types(value.type):
if t.code == gdb.TYPE_CODE_PTR:
for t2 in implemented_types(t.target()):
if t2.code == gdb.TYPE_CODE_TYPEDEF:
p = check_table(ptr_printers_by_tag, str(t2))
elif t2.code == gdb.TYPE_CODE_STRUCT:
p = check_table(ptr_printers_by_tag, t2.tag)
else:
p = None
if p: return p
else:
if t.code == gdb.TYPE_CODE_TYPEDEF:
p = check_table(printers_by_tag, str(t))
elif t.code == gdb.TYPE_CODE_STRUCT:
m = template_regexp.match(t.tag)
if m:
p = check_table(template_printers_by_tag, m.group(1))
else:
p = check_table(printers_by_tag, t.tag)
else:
p = None
if p: return p
# Failing that, look for a printer in printers_by_regexp. We have
# to scan the whole list, so regexp printers should be used
# sparingly.
s = str(value.type)
for (r, f) in printers_by_regexp:
if f.enabled:
m = r.match(s)
if m:
p = f(value, cache)
if p: return p
# No luck.
return None
# Give |lookup| the attributes expected of a pretty-printer with
# subprinters, for enabling and disabling.
lookup.name = "SpiderMonkey"
lookup.enabled = True
lookup.subprinters = subprinters
return lookup
# A base class for pretty-printers for pointer values that handles null
# pointers, by declining to construct a pretty-printer for them at all.
# Derived classes may simply assume that self.value is non-null.
#
# This class provides the following methods, which subclasses are free to
# override:
#
# __init__(self, value, cache): Save value and cache as properties by those names
# on the instance.
#
# to_string(self): format the type's name and address, as GDB would, and then
# call a 'summary' method (which the subclass must define) to produce a
# description of the referent.
#
# Note that pretty-printers returning a 'string' display hint must not use
# this default 'to_string' method, as GDB will take everything it returns,
# including the type name and address, as string contents.
class Pointer(object):
def __new__(cls, value, cache):
# Don't try to provide pretty-printers for NULL pointers.
if value == 0: return None
return super(Pointer, cls).__new__(cls)
def __init__(self, value, cache):
self.value = value
self.cache = cache
def to_string(self):
# See comment above.
assert not hasattr(self, 'display_hint') or self.display_hint() != 'string'
address = self.value.cast(self.cache.void_ptr_t)
try:
summary = self.summary()
except gdb.MemoryError as r:
summary = str(r)
v = '(%s) %s %s' % (self.value.type, address, summary)
return v
def summary(self):
raise NotImplementedError

48
js/src/gdb/progressbar.py Normal file
View File

@ -0,0 +1,48 @@
# 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/.
# Text progress bar library, like curl or scp.
import sys, datetime
class ProgressBar(object):
def __init__(self, label, limit, label_width=12):
self.label = label
self.limit = limit
self.label_width = label_width
self.cur = 0
self.t0 = datetime.datetime.now()
self.fullwidth = None
self.barlen = 64 - self.label_width
self.fmt = '\r%-' + str(label_width) + 's %3d%% %-' + str(self.barlen) + 's| %6.1fs'
def update(self, value):
self.cur = value
pct = int(100.0 * self.cur / self.limit)
barlen = int(1.0 * self.barlen * self.cur / self.limit) - 1
bar = '='*barlen + '>'
dt = datetime.datetime.now() - self.t0
dt = dt.seconds + dt.microseconds * 1e-6
line = self.fmt%(self.label[:self.label_width], pct, bar, dt)
self.fullwidth = len(line)
sys.stdout.write(line)
sys.stdout.flush()
# Clear the current bar and leave the cursor at the start of the line.
def clear(self):
if (self.fullwidth):
sys.stdout.write('\r' + ' ' * self.fullwidth + '\r')
self.fullwidth = None
def finish(self):
self.update(self.limit)
sys.stdout.write('\n')
if __name__ == '__main__':
pb = ProgressBar('test', 12)
for i in range(12):
pb.update(i)
time.sleep(0.5)
pb.finish()

349
js/src/gdb/run-tests.py Normal file
View File

@ -0,0 +1,349 @@
#!/usr/bin/env 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/.
# run-tests.py -- Python harness for GDB SpiderMonkey support
import os, re, subprocess, sys, traceback
from threading import Thread
# From this directory:
import progressbar
from taskpool import TaskPool, get_cpu_count
# Backported from Python 3.1 posixpath.py
def _relpath(path, start=None):
"""Return a relative version of a path"""
if not path:
raise ValueError("no path specified")
if start is None:
start = os.curdir
start_list = os.path.abspath(start).split(os.sep)
path_list = os.path.abspath(path).split(os.sep)
# Work out how much of the filepath is shared by start and path.
i = len(os.path.commonprefix([start_list, path_list]))
rel_list = [os.pardir] * (len(start_list)-i) + path_list[i:]
if not rel_list:
return os.curdir
return os.path.join(*rel_list)
os.path.relpath = _relpath
# Characters that need to be escaped when used in shell words.
shell_need_escapes = re.compile('[^\w\d%+,-./:=@\'"]', re.DOTALL)
# Characters that need to be escaped within double-quoted strings.
shell_dquote_escapes = re.compile('[^\w\d%+,-./:=@"]', re.DOTALL)
def make_shell_cmd(l):
def quote(s):
if shell_need_escapes.search(s):
if s.find("'") < 0:
return "'" + s + "'"
return '"' + shell_dquote_escapes.sub('\\g<0>', s) + '"'
return s
return ' '.join([quote(_) for _ in l])
# An instance of this class collects the lists of passing, failing, and
# timing-out tests, runs the progress bar, and prints a summary at the end.
class Summary(object):
class SummaryBar(progressbar.ProgressBar):
def __init__(self, limit):
super(Summary.SummaryBar, self).__init__('', limit, 24)
def start(self):
self.label = '[starting ]'
self.update(0)
def counts(self, run, failures, timeouts):
self.label = '[%4d|%4d|%4d|%4d]' % (run - failures, failures, timeouts, run)
self.update(run)
def __init__(self, num_tests):
self.run = 0
self.failures = [] # kind of judgemental; "unexpecteds"?
self.timeouts = []
if not OPTIONS.hide_progress:
self.bar = Summary.SummaryBar(num_tests)
# Progress bar control.
def start(self):
if not OPTIONS.hide_progress:
self.bar.start()
def update(self):
if not OPTIONS.hide_progress:
self.bar.counts(self.run, len(self.failures), len(self.timeouts))
# Call 'thunk' to show some output, while getting the progress bar out of the way.
def interleave_output(self, thunk):
if not OPTIONS.hide_progress:
self.bar.clear()
thunk()
self.update()
def passed(self, test):
self.run += 1
self.update()
def failed(self, test):
self.run += 1
self.failures.append(test)
self.update()
def timeout(self, test):
self.run += 1
self.timeouts.append(test)
self.update()
def finish(self):
if not OPTIONS.hide_progress:
self.bar.finish()
if self.failures:
print "tests failed:"
for test in self.failures:
test.show(sys.stdout)
if OPTIONS.worklist:
try:
with open(OPTIONS.worklist) as out:
for test in self.failures:
out.write(test.name + '\n')
except IOError as err:
sys.stderr.write("Error writing worklist file '%s': %s"
% (OPTIONS.worklist, err))
sys.exit(1)
if OPTIONS.write_failures:
try:
with open(OPTIONS.write_failures) as out:
for test in self.failures:
test.show(out)
except IOError as err:
sys.stderr.write("Error writing worklist file '%s': %s"
% (OPTIONS.write_failures, err))
sys.exit(1)
if self.timeouts:
print "tests timed out:"
for test in self.timeouts:
test.show(sys.stdout)
if self.failures or self.timeouts:
sys.exit(2)
class Test(TaskPool.Task):
def __init__(self, path, summary):
super(Test, self).__init__()
self.test_path = path # path to .py test file
self.summary = summary
# test.name is the name of the test relative to the top of the test
# directory. This is what we use to report failures and timeouts,
# and when writing test lists.
self.name = os.path.relpath(self.test_path, OPTIONS.testdir)
self.stdout = ''
self.stderr = ''
self.returncode = None
def cmd(self):
testlibdir = os.path.normpath(os.path.join(OPTIONS.testdir, '..', 'lib-for-tests'))
return [OPTIONS.gdb_executable,
'-nw', # Don't create a window (unnecessary?)
'-nx', # Don't read .gdbinit.
'--ex', 'add-auto-load-safe-path %s' % (OPTIONS.builddir,),
'--ex', 'set env LD_LIBRARY_PATH %s' % (OPTIONS.libdir,),
'--ex', 'file %s' % (os.path.join(OPTIONS.builddir, 'gdb-tests'),),
'--eval-command', 'python sys.path[0:0] = [%r]' % testlibdir,
'--python', os.path.join(testlibdir, 'catcher.py'),
os.path.join(testlibdir, 'prolog.py'),
self.test_path]
def start(self, pipe, deadline):
super(Test, self).start(pipe, deadline)
if OPTIONS.show_cmd:
self.summary.interleave_output(lambda: self.show_cmd(sys.stdout))
def onStdout(self, text):
self.stdout += text
def onStderr(self, text):
self.stderr += text
def onFinished(self, returncode):
self.returncode = returncode
if OPTIONS.show_output:
self.summary.interleave_output(lambda: self.show_output(sys.stdout))
if returncode != 0:
self.summary.failed(self)
else:
self.summary.passed(self)
def onTimeout(self):
self.summary.timeout(self)
def show_cmd(self, out):
print "Command: ", make_shell_cmd(self.cmd())
def show_output(self, out):
if self.stdout:
out.write('Standard output:')
out.write('\n' + self.stdout + '\n')
if self.stderr:
out.write('Standard error:')
out.write('\n' + self.stderr + '\n')
def show(self, out):
out.write(self.name + '\n')
if OPTIONS.write_failure_output:
out.write('Command: %s\n' % (make_shell_cmd(self.cmd()),))
self.show_output(out)
out.write('GDB exit code: %r\n' % (self.returncode,))
def find_tests(dir, substring = None):
ans = []
for dirpath, dirnames, filenames in os.walk(dir):
if dirpath == '.':
continue
for filename in filenames:
if not filename.endswith('.py'):
continue
test = os.path.join(dirpath, filename)
if substring is None or substring in os.path.relpath(test, dir):
ans.append(test)
return ans
def build_test_exec(builddir):
p = subprocess.check_call(['make', 'gdb-tests'], cwd=builddir)
def run_tests(tests, summary):
pool = TaskPool(tests, job_limit=OPTIONS.workercount, timeout=OPTIONS.timeout)
pool.run_all()
OPTIONS = None
def main(argv):
global OPTIONS
script_path = os.path.abspath(__file__)
script_dir = os.path.dirname(script_path)
# LIBDIR is the directory in which we find the SpiderMonkey shared
# library, to link against.
#
# The [TESTS] optional arguments are paths of test files relative
# to the jit-test/tests directory.
from optparse import OptionParser
op = OptionParser(usage='%prog [options] LIBDIR [TESTS...]')
op.add_option('-s', '--show-cmd', dest='show_cmd', action='store_true',
help='show GDB shell command run')
op.add_option('-o', '--show-output', dest='show_output', action='store_true',
help='show output from GDB')
op.add_option('-x', '--exclude', dest='exclude', action='append',
help='exclude given test dir or path')
op.add_option('-t', '--timeout', dest='timeout', type=float, default=150.0,
help='set test timeout in seconds')
op.add_option('-j', '--worker-count', dest='workercount', type=int,
help='Run [WORKERCOUNT] tests at a time')
op.add_option('--no-progress', dest='hide_progress', action='store_true',
help='hide progress bar')
op.add_option('--worklist', dest='worklist', metavar='FILE',
help='Read tests to run from [FILE] (or run all if [FILE] not found);\n'
'write failures back to [FILE]')
op.add_option('-r', '--read-tests', dest='read_tests', metavar='FILE',
help='Run test files listed in [FILE]')
op.add_option('-w', '--write-failures', dest='write_failures', metavar='FILE',
help='Write failing tests to [FILE]')
op.add_option('--write-failure-output', dest='write_failure_output', action='store_true',
help='With --write-failures=FILE, additionally write the output of failed tests to [FILE]')
op.add_option('--gdb', dest='gdb_executable', metavar='EXECUTABLE', default='gdb',
help='Run tests with [EXECUTABLE], rather than plain \'gdb\'.')
op.add_option('--srcdir', dest='srcdir',
default=os.path.abspath(os.path.join(script_dir, '..')),
help='Use SpiderMonkey sources in [SRCDIR].')
op.add_option('--testdir', dest='testdir', default=os.path.join(script_dir, 'tests'),
help='Find tests in [TESTDIR].')
op.add_option('--builddir', dest='builddir',
help='Build test executable in [BUILDDIR].')
(OPTIONS, args) = op.parse_args(argv)
if len(args) < 1:
op.error('missing LIBDIR argument')
OPTIONS.libdir = os.path.abspath(args[0])
test_args = args[1:]
if not OPTIONS.workercount:
OPTIONS.workercount = get_cpu_count()
# Compute default for OPTIONS.builddir now, since we've computed OPTIONS.libdir.
if not OPTIONS.builddir:
OPTIONS.builddir = os.path.join(OPTIONS.libdir, 'gdb')
test_set = set()
# All the various sources of test names accumulate.
if test_args:
for arg in test_args:
test_set.update(find_tests(OPTIONS.testdir, arg))
if OPTIONS.worklist:
try:
with open(OPTIONS.worklist) as f:
for line in f:
test_set.update(os.path.join(test_dir, line.strip('\n')))
except IOError:
# With worklist, a missing file means to start the process with
# the complete list of tests.
sys.stderr.write("Couldn't read worklist file '%s'; running all tests\n"
% (OPTIONS.worklist,))
test_set = set(find_tests(OPTIONS.testdir))
if OPTIONS.read_tests:
try:
with open(OPTIONS.read_tests) as f:
for line in f:
test_set.update(os.path.join(test_dir, line.strip('\n')))
except IOError as err:
sys.stderr.write("Error trying to read test file '%s': %s\n"
% (OPTIONS.read_tests, err))
sys.exit(1)
# If none of the above options were passed, and no tests were listed
# explicitly, use the complete set.
if not test_args and not OPTIONS.worklist and not OPTIONS.read_tests:
test_set = set(find_tests(OPTIONS.testdir))
if OPTIONS.exclude:
exclude_set = set()
for exclude in OPTIONS.exclude:
exclude_set.update(find_tests(test_dir, exclude))
test_set -= exclude_set
if not test_set:
sys.stderr.write("No tests found matching command line arguments.\n")
sys.exit(1)
summary = Summary(len(test_set))
test_list = [ Test(_, summary) for _ in sorted(test_set) ]
# Build the test executable from all the .cpp files found in the test
# directory tree.
try:
build_test_exec(OPTIONS.builddir)
except subprocess.CalledProcessError as err:
sys.stderr.write("Error building test executable: %s\n" % (err,))
sys.exit(1)
# Run the tests.
try:
summary.start()
run_tests(test_list, summary)
summary.finish()
except OSError as err:
sys.stderr.write("Error running tests: %s\n", (err,))
sys.exit(1)
sys.exit(0)
if __name__ == '__main__':
main(sys.argv[1:])

219
js/src/gdb/taskpool.py Normal file
View File

@ -0,0 +1,219 @@
import fcntl, os, select, time
from subprocess import Popen, PIPE
# Run a series of subprocesses. Try to keep up to a certain number going in
# parallel at any given time. Enforce time limits.
#
# This is implemented using non-blocking I/O, and so is Unix-specific.
#
# We assume that, if a task closes its standard error, then it's safe to
# wait for it to terminate. So an ill-behaved task that closes its standard
# output and then hangs will hang us, as well. However, as it takes special
# effort to close one's standard output, this seems unlikely to be a
# problem in practice.
class TaskPool(object):
# A task we should run in a subprocess. Users should subclass this and
# fill in the methods as given.
class Task(object):
def __init__(self):
self.pipe = None
self.start_time = None
# Record that this task is running, with |pipe| as its Popen object,
# and should time out at |deadline|.
def start(self, pipe, deadline):
self.pipe = pipe
self.deadline = deadline
# Return a shell command (a string or sequence of arguments) to be
# passed to Popen to run the task. The command will be given
# /dev/null as its standard input, and pipes as its standard output
# and error.
def cmd(self):
raise NotImplementedError
# TaskPool calls this method to report that the process wrote
# |string| to its standard output.
def onStdout(self, string):
raise NotImplementedError
# TaskPool calls this method to report that the process wrote
# |string| to its standard error.
def onStderr(self, string):
raise NotImplementedError
# TaskPool calls this method to report that the process terminated,
# yielding |returncode|.
def onFinished(self, returncode):
raise NotImplementedError
# TaskPool calls this method to report that the process timed out and
# was killed.
def onTimeout(self):
raise NotImplementedError
# If a task output handler (onStdout, onStderr) throws this, we terminate
# the task.
class TerminateTask(Exception):
pass
def __init__(self, tasks, cwd='.', job_limit=4, timeout=150):
self.pending = iter(tasks)
self.cwd = cwd
self.job_limit = job_limit
self.timeout = timeout
self.next_pending = self.get_next_pending()
# Set self.next_pending to the next task that has not yet been executed.
def get_next_pending(self):
try:
return self.pending.next()
except StopIteration:
return None
def run_all(self):
# The currently running tasks: a set of Task instances.
running = set()
with open(os.devnull, 'r') as devnull:
while True:
while len(running) < self.job_limit and self.next_pending:
t = self.next_pending
p = Popen(t.cmd(), bufsize=16384,
stdin=devnull, stdout=PIPE, stderr=PIPE,
cwd=self.cwd)
# Put the stdout and stderr pipes in non-blocking mode. See
# the post-'select' code below for details.
flags = fcntl.fcntl(p.stdout, fcntl.F_GETFL)
fcntl.fcntl(p.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
flags = fcntl.fcntl(p.stderr, fcntl.F_GETFL)
fcntl.fcntl(p.stderr, fcntl.F_SETFL, flags | os.O_NONBLOCK)
t.start(p, time.time() + self.timeout)
running.add(t)
self.next_pending = self.get_next_pending()
# If we have no tasks running, and the above wasn't able to
# start any new ones, then we must be done!
if not running:
break
# How many seconds do we have until the earliest deadline?
now = time.time()
secs_to_next_deadline = max(min([t.deadline for t in running]) - now, 0)
# Wait for output or a timeout.
stdouts_and_stderrs = ([t.pipe.stdout for t in running]
+ [t.pipe.stderr for t in running])
(readable,w,x) = select.select(stdouts_and_stderrs, [], [], secs_to_next_deadline)
finished = set()
terminate = set()
for t in running:
# Since we've placed the pipes in non-blocking mode, these
# 'read's will simply return as many bytes as are available,
# rather than blocking until they have accumulated the full
# amount requested (or reached EOF). The 'read's should
# never throw, since 'select' has told us there was
# something available.
if t.pipe.stdout in readable:
output = t.pipe.stdout.read(16384)
if output != "":
try:
t.onStdout(output)
except TerminateTask:
terminate.add(t)
if t.pipe.stderr in readable:
output = t.pipe.stderr.read(16384)
if output != "":
try:
t.onStderr(output)
except TerminateTask:
terminate.add(t)
else:
# We assume that, once a task has closed its stderr,
# it will soon terminate. If a task closes its
# stderr and then hangs, we'll hang too, here.
t.pipe.wait()
t.onFinished(t.pipe.returncode)
finished.add(t)
# Remove the finished tasks from the running set. (Do this here
# to avoid mutating the set while iterating over it.)
running -= finished
# Terminate any tasks whose handlers have asked us to do so.
for t in terminate:
t.pipe.terminate()
t.pipe.wait()
running.remove(t)
# Terminate any tasks which have missed their deadline.
finished = set()
for t in running:
if now >= t.deadline:
t.pipe.terminate()
t.pipe.wait()
t.onTimeout()
finished.add(t)
# Remove the finished tasks from the running set. (Do this here
# to avoid mutating the set while iterating over it.)
running -= finished
return None
def get_cpu_count():
"""
Guess at a reasonable parallelism count to set as the default for the
current machine and run.
"""
# Python 2.6+
try:
import multiprocessing
return multiprocessing.cpu_count()
except (ImportError,NotImplementedError):
pass
# POSIX
try:
res = int(os.sysconf('SC_NPROCESSORS_ONLN'))
if res > 0:
return res
except (AttributeError,ValueError):
pass
# Windows
try:
res = int(os.environ['NUMBER_OF_PROCESSORS'])
if res > 0:
return res
except (KeyError, ValueError):
pass
return 1
if __name__ == '__main__':
# Test TaskPool by using it to implement the unique 'sleep sort' algorithm.
def sleep_sort(ns, timeout):
sorted=[]
class SortableTask(TaskPool.Task):
def __init__(self, n):
super(SortableTask, self).__init__()
self.n = n
def start(self, pipe, deadline):
super(SortableTask, self).start(pipe, deadline)
def cmd(self):
return ['sh', '-c', 'echo out; sleep %d; echo err>&2' % (self.n,)]
def onStdout(self, text):
print '%d stdout: %r' % (self.n, text)
def onStderr(self, text):
print '%d stderr: %r' % (self.n, text)
def onFinished(self, returncode):
print '%d (rc=%d)' % (self.n, returncode)
sorted.append(self.n)
def onTimeout(self):
print '%d timed out' % (self.n,)
p = TaskPool([SortableTask(_) for _ in ns], job_limit=len(ns), timeout=timeout)
p.run_all()
return sorted
print repr(sleep_sort([1,1,2,3,5,8,13,21,34], 15))

View File

@ -0,0 +1,6 @@
gdb.execute('set print address on')
run_fragment('JSObject.null')
assert_pretty('null', '0x0')

View File

@ -0,0 +1,29 @@
#include "gdb-tests.h"
FRAGMENT(JSObject, simple) {
js::Rooted<JSObject *> glob(cx, JS_GetGlobalObject(cx));
js::Rooted<JSObject *> plain(cx, JS_NewObject(cx, 0, 0, 0));
js::Rooted<JSObject *> func(cx, (JSObject *) JS_NewFunction(cx, (JSNative) 1, 0, 0,
JS_GetGlobalObject(cx), "dys"));
js::Rooted<JSObject *> anon(cx, (JSObject *) JS_NewFunction(cx, (JSNative) 1, 0, 0,
JS_GetGlobalObject(cx), 0));
js::Rooted<JSFunction *> funcPtr(cx, JS_NewFunction(cx, (JSNative) 1, 0, 0,
JS_GetGlobalObject(cx), "formFollows"));
breakpoint();
(void) glob;
(void) plain;
(void) func;
(void) anon;
(void) funcPtr;
}
FRAGMENT(JSObject, null) {
js::Rooted<JSObject *> null(cx, NULL);
breakpoint();
(void) null;
}

View File

@ -0,0 +1,15 @@
# Printing JSObjects.
assert_subprinter_registered('SpiderMonkey', 'ptr-to-JSObject')
run_fragment('JSObject.simple')
# These patterns look a little strange because of prolog.py's 'set print
# address off', which avoids putting varying addresses in the output. After
# the '(JSObject *) ', there is a 'void *' value printing as the empty
# string.
assert_pretty('glob', '(JSObject *) [object global] delegate')
assert_pretty('plain', '(JSObject *) [object Object]')
assert_pretty('func', '(JSObject *) [object Function "dys"]')
assert_pretty('anon', '(JSObject *) [object Function <unnamed>]')
assert_pretty('funcPtr', '(JSFunction *) [object Function "formFollows"]')

View File

@ -0,0 +1,5 @@
gdb.execute('set print address on')
run_fragment('JSString.null')
assert_pretty('null', '0x0')

View File

@ -0,0 +1,5 @@
# We can print pointers to subclasses of JSString.
run_fragment('JSString.subclasses')
assert_pretty('flat', '"Hi!"')

View File

@ -0,0 +1,53 @@
#include "gdb-tests.h"
#include "jsatom.h"
FRAGMENT(JSString, simple) {
js::Rooted<JSString *> empty(cx, JS_NewStringCopyN(cx, NULL, 0));
js::Rooted<JSString *> x(cx, JS_NewStringCopyN(cx, "x", 1));
js::Rooted<JSString *> z(cx, JS_NewStringCopyZ(cx, "z"));
// I expect this will be a non-inlined string.
js::Rooted<JSString *> stars(cx, JS_NewStringCopyZ(cx,
"*************************"
"*************************"
"*************************"
"*************************"));
// This may well be an inlined string.
js::Rooted<JSString *> xz(cx, JS_ConcatStrings(cx, x, z));
// This will probably be a rope.
js::Rooted<JSString *> doubleStars(cx, JS_ConcatStrings(cx, stars, stars));
breakpoint();
(void) empty;
(void) x;
(void) z;
(void) stars;
(void) xz;
(void) doubleStars;
}
FRAGMENT(JSString, null) {
js::Rooted<JSString *> null(cx, NULL);
breakpoint();
(void) null;
}
FRAGMENT(JSString, subclasses) {
js::Rooted<JSFlatString *> flat(cx, JS_FlattenString(cx, JS_NewStringCopyZ(cx, "Hi!")));
breakpoint();
(void) flat;
}
FRAGMENT(JSString, atom) {
JSAtom *molybdenum = js::Atomize(cx, "molybdenum", 10);
breakpoint();
(void) molybdenum;
}

View File

@ -0,0 +1,21 @@
# Printing JSStrings.
assert_subprinter_registered('SpiderMonkey', 'ptr-to-JSString')
run_fragment('JSString.simple')
assert_pretty('empty', '""')
assert_pretty('x', '"x"')
assert_pretty('z', '"z"')
assert_pretty('xz', '"xz"')
stars = gdb.parse_and_eval('stars')
assert_eq(str(stars), "'*' <repeats 100 times>")
doubleStars = gdb.parse_and_eval('doubleStars')
assert_eq(str(doubleStars), "'*' <repeats 200 times>")
# JSAtom *
run_fragment('JSString.atom')
assert_pretty('molybdenum', '"molybdenum"')

View File

@ -0,0 +1,20 @@
# Test printing roots that refer to NULL pointers.
# Since mozilla.prettyprinters.Pointer declines to create pretty-printers
# for null pointers, GDB built-in printing code ends up handling them. But
# as of 2012-11, GDB suppresses printing pointers in replacement values:
# see: http://sourceware.org/ml/gdb/2012-11/msg00055.html
#
# Thus, if the pretty-printer for js::Rooted simply returns the referent as
# a replacement value (which seems reasonable enough, if you want the
# pretty-printer to be completely transparent), and the referent is a null
# pointer, it prints as nothing at all.
#
# This test ensures that the js::Rooted pretty-printer doesn't make that
# mistake.
gdb.execute('set print address on')
run_fragment('Root.null')
assert_pretty('null', '0x0')

View File

@ -0,0 +1,30 @@
#include "gdb-tests.h"
FRAGMENT(Root, null) {
js::Rooted<JSObject *> null(cx, NULL);
breakpoint();
(void) null;
}
void callee(JS::Handle<JSObject *> obj, JS::MutableHandle<JSObject *> mutableObj)
{
breakpoint();
}
FRAGMENT(Root, handle) {
js::Rooted<JSObject *> global(cx, JS_GetGlobalObject(cx));
callee(global, &global);
(void) global;
}
FRAGMENT(Root, HeapSlot) {
js::Rooted<jsval> plinth(cx, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, "plinth")));
js::Rooted<JSObject *> array(cx, JS_NewArrayObject(cx, 1, plinth.address()));
breakpoint();
(void) plinth;
(void) array;
}

View File

@ -0,0 +1,19 @@
# Test printing Handles.
assert_subprinter_registered('SpiderMonkey', 'instantiations-of-js::Rooted')
assert_subprinter_registered('SpiderMonkey', 'instantiations-of-JS::Handle')
assert_subprinter_registered('SpiderMonkey', 'instantiations-of-JS::MutableHandle')
assert_subprinter_registered('SpiderMonkey', 'instantiations-of-js::EncapsulatedPtr')
assert_subprinter_registered('SpiderMonkey', 'js::EncapsulatedValue')
run_fragment('Root.handle')
assert_pretty('obj', '(JSObject * const) [object global] delegate')
assert_pretty('mutableObj', '(JSObject *) [object global] delegate')
run_fragment('Root.HeapSlot')
# This depends on implementation details of arrays, but since HeapSlot is
# not a public type, I'm not sure how to avoid doing *something* ugly.
assert_pretty('array->elements[0]', '$jsval("plinth")')

View File

@ -0,0 +1,21 @@
#include "gdb-tests.h"
FRAGMENT(jsid, simple) {
js::Rooted<JSString *> string(cx, JS_NewStringCopyZ(cx, "moon"));
js::Rooted<JSString *> interned(cx, JS_InternJSString(cx, string));
js::Rooted<jsid> string_id(cx, INTERNED_STRING_TO_JSID(cx, interned));
jsid int_id = INT_TO_JSID(1729);
jsid void_id = JSID_VOID;
js::Rooted<jsid> object_id(cx, OBJECT_TO_JSID(JS_GetGlobalObject(cx)));
jsid xml_id = JS_DEFAULT_XML_NAMESPACE_ID;
breakpoint();
(void) string;
(void) interned;
(void) string_id;
(void) int_id;
(void) void_id;
(void) object_id;
(void) xml_id;
}

View File

@ -0,0 +1,11 @@
# Tests for jsid pretty-printing
assert_subprinter_registered('SpiderMonkey', 'jsid')
run_fragment('jsid.simple')
assert_pretty('string_id', '$jsid("moon")')
assert_pretty('int_id', '$jsid(1729)')
assert_pretty('void_id', 'JSID_VOID')
assert_pretty('object_id', '$jsid((JSObject *) [object global] delegate)')
assert_pretty('xml_id', 'JS_DEFAULT_XML_NAMESPACE_ID')

View File

@ -0,0 +1,35 @@
#include "gdb-tests.h"
FRAGMENT(jsval, simple) {
js::Rooted<jsval> fortytwo(cx, INT_TO_JSVAL(42));
js::Rooted<jsval> negone(cx, INT_TO_JSVAL(-1));
js::Rooted<jsval> undefined(cx, JSVAL_VOID);
js::Rooted<jsval> null(cx, JSVAL_NULL);
js::Rooted<jsval> js_true(cx, JSVAL_TRUE);
js::Rooted<jsval> js_false(cx, JSVAL_FALSE);
js::Rooted<jsval> array_hole(cx, js::MagicValue(JS_ARRAY_HOLE));
js::Rooted<jsval> empty_string(cx);
empty_string.setString(JS_NewStringCopyZ(cx, ""));
js::Rooted<jsval> friendly_string(cx);
friendly_string.setString(JS_NewStringCopyZ(cx, "Hello!"));
js::Rooted<jsval> global(cx);
global.setObject(*JS_GetGlobalObject(cx));
// Some interesting value that floating-point won't munge.
js::Rooted<jsval> onehundredthirtysevenonehundredtwentyeighths(cx, DOUBLE_TO_JSVAL(137.0 / 128.0));
breakpoint();
(void) fortytwo;
(void) negone;
(void) undefined;
(void) js_true;
(void) js_false;
(void) null;
(void) array_hole;
(void) empty_string;
(void) friendly_string;
(void) global;
}

View File

@ -0,0 +1,17 @@
# Basic unit tests for jsval pretty-printer.
assert_subprinter_registered('SpiderMonkey', 'JS::Value')
run_fragment('jsval.simple')
assert_pretty('fortytwo', '$jsval(42)')
assert_pretty('negone', '$jsval(-1)')
assert_pretty('undefined', 'JSVAL_VOID')
assert_pretty('null', 'JSVAL_NULL')
assert_pretty('js_true', 'JSVAL_TRUE')
assert_pretty('js_false', 'JSVAL_FALSE')
assert_pretty('array_hole', '$jsmagic(JS_ARRAY_HOLE)')
assert_pretty('empty_string', '$jsval("")')
assert_pretty('friendly_string', '$jsval("Hello!")')
assert_pretty('global', '$jsval((JSObject *) [object global] delegate)')
assert_pretty('onehundredthirtysevenonehundredtwentyeighths', '$jsval(1.0703125)')

View File

@ -0,0 +1,38 @@
#include "gdb-tests.h"
typedef int A;
typedef A B;
class C { };
class D { };
typedef C C_;
typedef D D_;
class E: C, D { };
typedef E E_;
class F: C_, D_ { };
class G { };
class H: F, G { };
FRAGMENT(prettyprinters, implemented_types) {
int i;
A a;
B b;
C c;
C_ c_;
E e;
E_ e_;
F f;
H h;
breakpoint();
(void) i;
(void) a;
(void) b;
(void) c;
(void) c_;
(void) e;
(void) e_;
(void) f;
(void) h;
}

View File

@ -0,0 +1,18 @@
import mozilla.prettyprinters
run_fragment('prettyprinters.implemented_types')
def implemented_type_names(expr):
v = gdb.parse_and_eval(expr)
it = mozilla.prettyprinters.implemented_types(v.type)
return [str(_) for _ in it]
assert_eq(implemented_type_names('i'), ['int'])
assert_eq(implemented_type_names('a'), ['A', 'int'])
assert_eq(implemented_type_names('b'), ['B', 'A', 'int'])
assert_eq(implemented_type_names('c'), ['C'])
assert_eq(implemented_type_names('c_'), ['C_', 'C'])
assert_eq(implemented_type_names('e'), ['E', 'C', 'D'])
assert_eq(implemented_type_names('e_'), ['E_', 'E', 'C', 'D'])
assert_eq(implemented_type_names('f'), ['F', 'C', 'D'])
assert_eq(implemented_type_names('h'), ['H', 'F', 'G', 'C', 'D'])

View File

@ -0,0 +1,11 @@
#include "gdb-tests.h"
typedef int my_typedef;
FRAGMENT(typedef_printers, one) {
my_typedef i = 0;
breakpoint();
(void) i;
}

View File

@ -0,0 +1,14 @@
# Test that we can find pretty-printers for typedef names, not just for
# struct types and templates.
import mozilla.prettyprinters
@mozilla.prettyprinters.pretty_printer('my_typedef')
class my_typedef(object):
def __init__(self, value, cache):
pass
def to_string(self):
return 'huzzah'
run_fragment('typedef_printers.one')
assert_pretty('i', 'huzzah')

View File

@ -91,6 +91,12 @@ ifdef QEMU_EXE
MOZ_POST_PROGRAM_COMMAND = $(topsrcdir)/build/qemu-wrap --qemu $(QEMU_EXE) --libdir $(CROSS_LIB)
endif
# Place a GDB Python auto-load file next to the jsapi-tests executable in
# the build directory.
PP_TARGETS += JSAPI_TESTS_AUTOLOAD
JSAPI_TESTS_AUTOLOAD := jsapi-tests-gdb.py.in
JSAPI_TESTS_AUTOLOAD_FLAGS := -Dtopsrcdir=$(abspath $(topsrcdir))
include $(topsrcdir)/config/rules.mk
check::

View File

@ -0,0 +1,8 @@
"""GDB Python customization auto-loader for jsapi-tests executable"""
#filter substitution
import os.path
sys.path[0:0] = [os.path.join('@topsrcdir@', 'gdb')]
import mozilla.autoload
mozilla.autoload.register(gdb.current_objfile())

View File

@ -47,6 +47,16 @@ ifdef QEMU_EXE
MOZ_POST_PROGRAM_COMMAND = $(topsrcdir)/build/qemu-wrap --qemu $(QEMU_EXE) --libdir $(CROSS_LIB)
endif
# Place a GDB Python auto-load file next to the shell executable, both in
# the build directory and in the dist/bin directory.
PP_TARGETS += SHELL_AUTOLOAD
SHELL_AUTOLOAD := js-gdb.py.in
SHELL_AUTOLOAD_FLAGS := -Dtopsrcdir=$(abspath $(topsrcdir))
INSTALL_TARGETS += SHELL_INSTALL_AUTOLOAD
SHELL_INSTALL_AUTOLOAD_FILES := $(CURDIR)/js-gdb.py
SHELL_INSTALL_AUTOLOAD_DEST := $(DIST)/bin
include $(topsrcdir)/config/rules.mk
ifdef MOZ_SHARK

View File

@ -0,0 +1,8 @@
""" GDB Python customization auto-loader for js shell """
#filter substitution
import os.path
sys.path[0:0] = [os.path.join('@topsrcdir@', 'gdb')]
import mozilla.autoload
mozilla.autoload.register(gdb.current_objfile())