mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 506717: GDB pretty-printing support for SpiderMonkey. r=sfink,ted
This commit is contained in:
parent
8cd6a3db7a
commit
846be8349c
@ -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
44
js/src/gdb/Makefile.in
Normal 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
197
js/src/gdb/README
Normal 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
22
js/src/gdb/TODO
Normal 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:
|
8
js/src/gdb/gdb-tests-gdb.py.in
Normal file
8
js/src/gdb/gdb-tests-gdb.py.in
Normal 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
91
js/src/gdb/gdb-tests.cpp
Normal 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
70
js/src/gdb/gdb-tests.h
Normal 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
|
||||
|
19
js/src/gdb/lib-for-tests/catcher.py
Normal file
19
js/src/gdb/lib-for-tests/catcher.py
Normal 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)
|
69
js/src/gdb/lib-for-tests/prolog.py
Normal file
69
js/src/gdb/lib-for-tests/prolog.py
Normal 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)
|
||||
|
42
js/src/gdb/mozilla/JSObject.py
Normal file
42
js/src/gdb/mozilla/JSObject.py
Normal 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 '')
|
55
js/src/gdb/mozilla/JSString.py
Normal file
55
js/src/gdb/mozilla/JSString.py
Normal 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)
|
63
js/src/gdb/mozilla/Root.py
Normal file
63
js/src/gdb/mozilla/Root.py
Normal 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
|
1
js/src/gdb/mozilla/__init__.py
Normal file
1
js/src/gdb/mozilla/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# Yes, Python, this is a package.
|
24
js/src/gdb/mozilla/autoload.py
Normal file
24
js/src/gdb/mozilla/autoload.py
Normal 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)
|
56
js/src/gdb/mozilla/jsid.py
Normal file
56
js/src/gdb/mozilla/jsid.py
Normal 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
219
js/src/gdb/mozilla/jsval.py
Normal 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)
|
313
js/src/gdb/mozilla/prettyprinters.py
Normal file
313
js/src/gdb/mozilla/prettyprinters.py
Normal 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
48
js/src/gdb/progressbar.py
Normal 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
349
js/src/gdb/run-tests.py
Normal 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
219
js/src/gdb/taskpool.py
Normal 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))
|
6
js/src/gdb/tests/test-JSObject-null.py
Normal file
6
js/src/gdb/tests/test-JSObject-null.py
Normal file
@ -0,0 +1,6 @@
|
||||
gdb.execute('set print address on')
|
||||
|
||||
run_fragment('JSObject.null')
|
||||
|
||||
assert_pretty('null', '0x0')
|
||||
|
29
js/src/gdb/tests/test-JSObject.cpp
Normal file
29
js/src/gdb/tests/test-JSObject.cpp
Normal 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;
|
||||
}
|
||||
|
15
js/src/gdb/tests/test-JSObject.py
Normal file
15
js/src/gdb/tests/test-JSObject.py
Normal 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"]')
|
5
js/src/gdb/tests/test-JSString-null.py
Normal file
5
js/src/gdb/tests/test-JSString-null.py
Normal file
@ -0,0 +1,5 @@
|
||||
gdb.execute('set print address on')
|
||||
|
||||
run_fragment('JSString.null')
|
||||
|
||||
assert_pretty('null', '0x0')
|
5
js/src/gdb/tests/test-JSString-subclasses.py
Normal file
5
js/src/gdb/tests/test-JSString-subclasses.py
Normal file
@ -0,0 +1,5 @@
|
||||
# We can print pointers to subclasses of JSString.
|
||||
|
||||
run_fragment('JSString.subclasses')
|
||||
|
||||
assert_pretty('flat', '"Hi!"')
|
53
js/src/gdb/tests/test-JSString.cpp
Normal file
53
js/src/gdb/tests/test-JSString.cpp
Normal 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;
|
||||
}
|
21
js/src/gdb/tests/test-JSString.py
Normal file
21
js/src/gdb/tests/test-JSString.py
Normal 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"')
|
20
js/src/gdb/tests/test-Root-null.py
Normal file
20
js/src/gdb/tests/test-Root-null.py
Normal 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')
|
30
js/src/gdb/tests/test-Root.cpp
Normal file
30
js/src/gdb/tests/test-Root.cpp
Normal 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;
|
||||
}
|
19
js/src/gdb/tests/test-Root.py
Normal file
19
js/src/gdb/tests/test-Root.py
Normal 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")')
|
||||
|
21
js/src/gdb/tests/test-jsid.cpp
Normal file
21
js/src/gdb/tests/test-jsid.cpp
Normal 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;
|
||||
}
|
11
js/src/gdb/tests/test-jsid.py
Normal file
11
js/src/gdb/tests/test-jsid.py
Normal 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')
|
35
js/src/gdb/tests/test-jsval.cpp
Normal file
35
js/src/gdb/tests/test-jsval.cpp
Normal 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;
|
||||
}
|
17
js/src/gdb/tests/test-jsval.py
Normal file
17
js/src/gdb/tests/test-jsval.py
Normal 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)')
|
38
js/src/gdb/tests/test-prettyprinters.cpp
Normal file
38
js/src/gdb/tests/test-prettyprinters.cpp
Normal 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;
|
||||
}
|
18
js/src/gdb/tests/test-prettyprinters.py
Normal file
18
js/src/gdb/tests/test-prettyprinters.py
Normal 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'])
|
11
js/src/gdb/tests/typedef-printers.cpp
Normal file
11
js/src/gdb/tests/typedef-printers.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include "gdb-tests.h"
|
||||
|
||||
typedef int my_typedef;
|
||||
|
||||
FRAGMENT(typedef_printers, one) {
|
||||
my_typedef i = 0;
|
||||
|
||||
breakpoint();
|
||||
|
||||
(void) i;
|
||||
}
|
14
js/src/gdb/tests/typedef-printers.py
Normal file
14
js/src/gdb/tests/typedef-printers.py
Normal 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')
|
@ -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::
|
||||
|
8
js/src/jsapi-tests/jsapi-tests-gdb.py.in
Normal file
8
js/src/jsapi-tests/jsapi-tests-gdb.py.in
Normal 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())
|
@ -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
|
||||
|
8
js/src/shell/js-gdb.py.in
Normal file
8
js/src/shell/js-gdb.py.in
Normal 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())
|
Loading…
Reference in New Issue
Block a user