blob: 08fc0bb81dbb053e1e738a7eee592744e10bf105 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 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/. */
/*
* JS debugging API.
*/
#include "jsdbgapi.h"
#include <string.h>
#include "jsprvtd.h"
#include "jstypes.h"
#include "jsapi.h"
#include "jscntxt.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jsobj.h"
#include "jsopcode.h"
#include "jsscript.h"
#include "jsstr.h"
#include "jswatchpoint.h"
#include "frontend/SourceNotes.h"
#include "jit/AsmJS.h"
#include "vm/Debugger.h"
#include "vm/Interpreter.h"
#include "vm/Shape.h"
#ifdef JS_ION
#include "jit/AsmJSModule.h"
#endif
#include "jsatominlines.h"
#include "jsinferinlines.h"
#include "jsscriptinlines.h"
#include "vm/Debugger-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
using namespace js::gc;
using mozilla::PodZero;
JS_PUBLIC_API(JSBool)
JS_GetDebugMode(JSContext *cx)
{
return cx->compartment()->debugMode();
}
JS_PUBLIC_API(JSBool)
JS_SetDebugMode(JSContext *cx, JSBool debug)
{
return JS_SetDebugModeForCompartment(cx, cx->compartment(), debug);
}
JS_PUBLIC_API(void)
JS_SetRuntimeDebugMode(JSRuntime *rt, JSBool debug)
{
rt->debugMode = !!debug;
}
static bool
IsTopFrameConstructing(JSContext *cx, AbstractFramePtr frame)
{
ScriptFrameIter iter(cx);
JS_ASSERT(iter.abstractFramePtr() == frame);
return iter.isConstructing();
}
JSTrapStatus
js::ScriptDebugPrologue(JSContext *cx, AbstractFramePtr frame)
{
JS_ASSERT_IF(frame.isStackFrame(), frame.asStackFrame() == cx->interpreterFrame());
if (!frame.script()->selfHosted) {
if (frame.isFramePushedByExecute()) {
if (JSInterpreterHook hook = cx->runtime()->debugHooks.executeHook)
frame.setHookData(hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame),
true, 0, cx->runtime()->debugHooks.executeHookData));
} else {
if (JSInterpreterHook hook = cx->runtime()->debugHooks.callHook)
frame.setHookData(hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame),
true, 0, cx->runtime()->debugHooks.callHookData));
}
}
RootedValue rval(cx);
JSTrapStatus status = Debugger::onEnterFrame(cx, frame, &rval);
switch (status) {
case JSTRAP_CONTINUE:
break;
case JSTRAP_THROW:
cx->setPendingException(rval);
break;
case JSTRAP_ERROR:
cx->clearPendingException();
break;
case JSTRAP_RETURN:
frame.setReturnValue(rval);
break;
default:
JS_NOT_REACHED("bad Debugger::onEnterFrame JSTrapStatus value");
}
return status;
}
bool
js::ScriptDebugEpilogue(JSContext *cx, AbstractFramePtr frame, bool okArg)
{
JS_ASSERT_IF(frame.isStackFrame(), frame.asStackFrame() == cx->interpreterFrame());
JSBool ok = okArg;
// We don't add hook data for self-hosted scripts, so we don't need to check for them, here.
if (void *hookData = frame.maybeHookData()) {
if (frame.isFramePushedByExecute()) {
if (JSInterpreterHook hook = cx->runtime()->debugHooks.executeHook)
hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame), false, &ok, hookData);
} else {
if (JSInterpreterHook hook = cx->runtime()->debugHooks.callHook)
hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame), false, &ok, hookData);
}
}
return Debugger::onLeaveFrame(cx, frame, ok);
}
JSTrapStatus
js::DebugExceptionUnwind(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
{
JS_ASSERT(cx->compartment()->debugMode());
if (!cx->runtime()->debugHooks.throwHook && cx->compartment()->getDebuggees().empty())
return JSTRAP_CONTINUE;
/* Call debugger throw hook if set. */
RootedValue rval(cx);
JSTrapStatus status = Debugger::onExceptionUnwind(cx, &rval);
if (status == JSTRAP_CONTINUE) {
if (JSThrowHook handler = cx->runtime()->debugHooks.throwHook) {
RootedScript script(cx, frame.script());
status = handler(cx, script, pc, rval.address(), cx->runtime()->debugHooks.throwHookData);
}
}
switch (status) {
case JSTRAP_ERROR:
cx->clearPendingException();
break;
case JSTRAP_RETURN:
cx->clearPendingException();
frame.setReturnValue(rval);
break;
case JSTRAP_THROW:
cx->setPendingException(rval);
break;
case JSTRAP_CONTINUE:
break;
default:
JS_NOT_REACHED("Invalid trap status");
}
return status;
}
JS_FRIEND_API(JSBool)
JS_SetDebugModeForAllCompartments(JSContext *cx, JSBool debug)
{
AutoDebugModeGC dmgc(cx->runtime());
for (CompartmentsIter c(cx->runtime()); !c.done(); c.next()) {
// Ignore special compartments (atoms, JSD compartments)
if (c->principals) {
if (!c->setDebugModeFromC(cx, !!debug, dmgc))
return false;
}
}
return true;
}
JS_FRIEND_API(JSBool)
JS_SetDebugModeForCompartment(JSContext *cx, JSCompartment *comp, JSBool debug)
{
AutoDebugModeGC dmgc(cx->runtime());
return comp->setDebugModeFromC(cx, !!debug, dmgc);
}
static JSBool
CheckDebugMode(JSContext *cx)
{
JSBool debugMode = JS_GetDebugMode(cx);
/*
* :TODO:
* This probably should be an assertion, since it's indicative of a severe
* API misuse.
*/
if (!debugMode) {
JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage,
NULL, JSMSG_NEED_DEBUG_MODE);
}
return debugMode;
}
JS_PUBLIC_API(JSBool)
JS_SetSingleStepMode(JSContext *cx, JSScript *scriptArg, JSBool singleStep)
{
RootedScript script(cx, scriptArg);
assertSameCompartment(cx, script);
if (!CheckDebugMode(cx))
return JS_FALSE;
return script->setStepModeFlag(cx, singleStep);
}
JS_PUBLIC_API(JSBool)
JS_SetTrap(JSContext *cx, JSScript *scriptArg, jsbytecode *pc, JSTrapHandler handler, jsval closureArg)
{
RootedScript script(cx, scriptArg);
RootedValue closure(cx, closureArg);
assertSameCompartment(cx, script, closure);
if (!CheckDebugMode(cx))
return false;
BreakpointSite *site = script->getOrCreateBreakpointSite(cx, pc);
if (!site)
return false;
site->setTrap(cx->runtime()->defaultFreeOp(), handler, closure);
return true;
}
JS_PUBLIC_API(void)
JS_ClearTrap(JSContext *cx, JSScript *script, jsbytecode *pc,
JSTrapHandler *handlerp, jsval *closurep)
{
if (BreakpointSite *site = script->getBreakpointSite(pc)) {
site->clearTrap(cx->runtime()->defaultFreeOp(), handlerp, closurep);
} else {
if (handlerp)
*handlerp = NULL;
if (closurep)
*closurep = JSVAL_VOID;
}
}
JS_PUBLIC_API(void)
JS_ClearScriptTraps(JSRuntime *rt, JSScript *script)
{
script->clearTraps(rt->defaultFreeOp());
}
JS_PUBLIC_API(void)
JS_ClearAllTrapsForCompartment(JSContext *cx)
{
cx->compartment()->clearTraps(cx->runtime()->defaultFreeOp());
}
JS_PUBLIC_API(JSBool)
JS_SetInterrupt(JSRuntime *rt, JSInterruptHook hook, void *closure)
{
rt->debugHooks.interruptHook = hook;
rt->debugHooks.interruptHookData = closure;
for (InterpreterFrames *f = rt->interpreterFrames; f; f = f->older)
f->enableInterruptsUnconditionally();
return true;
}
JS_PUBLIC_API(JSBool)
JS_ClearInterrupt(JSRuntime *rt, JSInterruptHook *hoop, void **closurep)
{
if (hoop)
*hoop = rt->debugHooks.interruptHook;
if (closurep)
*closurep = rt->debugHooks.interruptHookData;
rt->debugHooks.interruptHook = 0;
rt->debugHooks.interruptHookData = 0;
return JS_TRUE;
}
/************************************************************************/
JS_PUBLIC_API(JSBool)
JS_SetWatchPoint(JSContext *cx, JSObject *obj_, jsid id_,
JSWatchPointHandler handler, JSObject *closure_)
{
assertSameCompartment(cx, obj_);
RootedId id(cx, id_);
RootedObject origobj(cx, obj_), closure(cx, closure_);
RootedObject obj(cx, GetInnerObject(cx, origobj));
if (!obj)
return false;
RootedValue v(cx);
unsigned attrs;
RootedId propid(cx);
if (JSID_IS_INT(id)) {
propid = id;
} else if (JSID_IS_OBJECT(id)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_WATCH_PROP);
return false;
} else {
RootedValue val(cx, IdToValue(id));
if (!ValueToId<CanGC>(cx, val, &propid))
return false;
}
/*
* If, by unwrapping and innerizing, we changed the object, check
* again to make sure that we're allowed to set a watch point.
*/
if (origobj != obj && !CheckAccess(cx, obj, propid, JSACC_WATCH, &v, &attrs))
return false;
if (!obj->isNative()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_WATCH,
obj->getClass()->name);
return false;
}
/*
* Use sparse indexes for watched objects, as dense elements can be written
* to without checking the watchpoint map.
*/
if (!JSObject::sparsifyDenseElements(cx, obj))
return false;
types::MarkTypePropertyConfigured(cx, obj, propid);
WatchpointMap *wpmap = cx->compartment()->watchpointMap;
if (!wpmap) {
wpmap = cx->runtime()->new_<WatchpointMap>();
if (!wpmap || !wpmap->init()) {
js_ReportOutOfMemory(cx);
return false;
}
cx->compartment()->watchpointMap = wpmap;
}
return wpmap->watch(cx, obj, propid, handler, closure);
}
JS_PUBLIC_API(JSBool)
JS_ClearWatchPoint(JSContext *cx, JSObject *obj, jsid id,
JSWatchPointHandler *handlerp, JSObject **closurep)
{
assertSameCompartment(cx, obj, id);
if (WatchpointMap *wpmap = cx->compartment()->watchpointMap)
wpmap->unwatch(obj, id, handlerp, closurep);
return true;
}
JS_PUBLIC_API(JSBool)
JS_ClearWatchPointsForObject(JSContext *cx, JSObject *obj)
{
assertSameCompartment(cx, obj);
if (WatchpointMap *wpmap = cx->compartment()->watchpointMap)
wpmap->unwatchObject(obj);
return true;
}
JS_PUBLIC_API(JSBool)
JS_ClearAllWatchPoints(JSContext *cx)
{
if (JSCompartment *comp = cx->compartment()) {
if (WatchpointMap *wpmap = comp->watchpointMap)
wpmap->clear();
}
return true;
}
/************************************************************************/
JS_PUBLIC_API(unsigned)
JS_PCToLineNumber(JSContext *cx, JSScript *script, jsbytecode *pc)
{
return js::PCToLineNumber(script, pc);
}
JS_PUBLIC_API(jsbytecode *)
JS_LineNumberToPC(JSContext *cx, JSScript *script, unsigned lineno)
{
return js_LineNumberToPC(script, lineno);
}
JS_PUBLIC_API(jsbytecode *)
JS_EndPC(JSContext *cx, JSScript *script)
{
return script->code + script->length;
}
JS_PUBLIC_API(JSBool)
JS_GetLinePCs(JSContext *cx, JSScript *script,
unsigned startLine, unsigned maxLines,
unsigned* count, unsigned** retLines, jsbytecode*** retPCs)
{
size_t len = (script->length > maxLines ? maxLines : script->length);
unsigned *lines = cx->pod_malloc<unsigned>(len);
if (!lines)
return JS_FALSE;
jsbytecode **pcs = cx->pod_malloc<jsbytecode*>(len);
if (!pcs) {
js_free(lines);
return JS_FALSE;
}
unsigned lineno = script->lineno;
unsigned offset = 0;
unsigned i = 0;
for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) {
offset += SN_DELTA(sn);
SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
if (type == SRC_SETLINE || type == SRC_NEWLINE) {
if (type == SRC_SETLINE)
lineno = (unsigned) js_GetSrcNoteOffset(sn, 0);
else
lineno++;
if (lineno >= startLine) {
lines[i] = lineno;
pcs[i] = script->code + offset;
if (++i >= maxLines)
break;
}
}
}
*count = i;
if (retLines)
*retLines = lines;
else
js_free(lines);
if (retPCs)
*retPCs = pcs;
else
js_free(pcs);
return JS_TRUE;
}
JS_PUBLIC_API(unsigned)
JS_GetFunctionArgumentCount(JSContext *cx, JSFunction *fun)
{
return fun->nargs;
}
JS_PUBLIC_API(JSBool)
JS_FunctionHasLocalNames(JSContext *cx, JSFunction *fun)
{
return fun->nonLazyScript()->bindings.count() > 0;
}
extern JS_PUBLIC_API(uintptr_t *)
JS_GetFunctionLocalNameArray(JSContext *cx, JSFunction *fun, void **memp)
{
RootedScript script(cx, fun->nonLazyScript());
BindingVector bindings(cx);
if (!FillBindingVector(script, &bindings))
return NULL;
LifoAlloc &lifo = cx->tempLifoAlloc();
// Store the LifoAlloc::Mark right before the allocation.
LifoAlloc::Mark mark = lifo.mark();
void *mem = lifo.alloc(sizeof(LifoAlloc::Mark) + bindings.length() * sizeof(uintptr_t));
if (!mem) {
js_ReportOutOfMemory(cx);
return NULL;
}
*memp = mem;
*reinterpret_cast<LifoAlloc::Mark*>(mem) = mark;
// Munge data into the API this method implements. Avert your eyes!
uintptr_t *names = reinterpret_cast<uintptr_t*>((char*)mem + sizeof(LifoAlloc::Mark));
for (size_t i = 0; i < bindings.length(); i++)
names[i] = reinterpret_cast<uintptr_t>(bindings[i].name());
return names;
}
extern JS_PUBLIC_API(JSAtom *)
JS_LocalNameToAtom(uintptr_t w)
{
return reinterpret_cast<JSAtom *>(w);
}
extern JS_PUBLIC_API(JSString *)
JS_AtomKey(JSAtom *atom)
{
return atom;
}
extern JS_PUBLIC_API(void)
JS_ReleaseFunctionLocalNameArray(JSContext *cx, void *mem)
{
cx->tempLifoAlloc().release(*reinterpret_cast<LifoAlloc::Mark*>(mem));
}
JS_PUBLIC_API(JSScript *)
JS_GetFunctionScript(JSContext *cx, JSFunction *fun)
{
if (fun->isNative())
return NULL;
if (fun->isInterpretedLazy()) {
RootedFunction rootedFun(cx, fun);
AutoCompartment funCompartment(cx, rootedFun);
JSScript *script = rootedFun->getOrCreateScript(cx);
if (!script)
MOZ_CRASH();
return script;
}
return fun->nonLazyScript();
}
JS_PUBLIC_API(JSNative)
JS_GetFunctionNative(JSContext *cx, JSFunction *fun)
{
return fun->maybeNative();
}
JS_PUBLIC_API(JSPrincipals *)
JS_GetScriptPrincipals(JSScript *script)
{
return script->principals();
}
JS_PUBLIC_API(JSPrincipals *)
JS_GetScriptOriginPrincipals(JSScript *script)
{
return script->originPrincipals;
}
/************************************************************************/
JS_PUBLIC_API(JSFunction *)
JS_GetScriptFunction(JSContext *cx, JSScript *script)
{
return script->function();
}
JS_PUBLIC_API(JSObject *)
JS_GetParentOrScopeChain(JSContext *cx, JSObject *obj)
{
return obj->enclosingScope();
}
JS_PUBLIC_API(const char *)
JS_GetDebugClassName(JSObject *obj)
{
if (obj->is<DebugScopeObject>())
return obj->as<DebugScopeObject>().scope().getClass()->name;
return obj->getClass()->name;
}
/************************************************************************/
JS_PUBLIC_API(const char *)
JS_GetScriptFilename(JSContext *cx, JSScript *script)
{
return script->filename();
}
JS_PUBLIC_API(const jschar *)
JS_GetScriptSourceMap(JSContext *cx, JSScript *script)
{
ScriptSource *source = script->scriptSource();
JS_ASSERT(source);
return source->hasSourceMap() ? source->sourceMap() : NULL;
}
JS_PUBLIC_API(unsigned)
JS_GetScriptBaseLineNumber(JSContext *cx, JSScript *script)
{
return script->lineno;
}
JS_PUBLIC_API(unsigned)
JS_GetScriptLineExtent(JSContext *cx, JSScript *script)
{
return js_GetScriptLineExtent(script);
}
JS_PUBLIC_API(JSVersion)
JS_GetScriptVersion(JSContext *cx, JSScript *script)
{
return VersionNumber(script->getVersion());
}
JS_PUBLIC_API(bool)
JS_GetScriptIsSelfHosted(JSScript *script)
{
return script->selfHosted;
}
/***************************************************************************/
JS_PUBLIC_API(void)
JS_SetNewScriptHook(JSRuntime *rt, JSNewScriptHook hook, void *callerdata)
{
rt->debugHooks.newScriptHook = hook;
rt->debugHooks.newScriptHookData = callerdata;
}
JS_PUBLIC_API(void)
JS_SetDestroyScriptHook(JSRuntime *rt, JSDestroyScriptHook hook,
void *callerdata)
{
rt->debugHooks.destroyScriptHook = hook;
rt->debugHooks.destroyScriptHookData = callerdata;
}
/***************************************************************************/
/* This all should be reworked to avoid requiring JSScopeProperty types. */
static JSBool
GetPropertyDesc(JSContext *cx, JSObject *obj_, HandleShape shape, JSPropertyDesc *pd)
{
assertSameCompartment(cx, obj_);
pd->id = IdToJsval(shape->propid());
RootedObject obj(cx, obj_);
JSBool wasThrowing = cx->isExceptionPending();
RootedValue lastException(cx, UndefinedValue());
if (wasThrowing)
lastException = cx->getPendingException();
cx->clearPendingException();
Rooted<jsid> id(cx, shape->propid());
RootedValue value(cx);
if (!baseops::GetProperty(cx, obj, id, &value)) {
if (!cx->isExceptionPending()) {
pd->flags = JSPD_ERROR;
pd->value = JSVAL_VOID;
} else {
pd->flags = JSPD_EXCEPTION;
pd->value = cx->getPendingException();
}
} else {
pd->flags = 0;
pd->value = value;
}
if (wasThrowing)
cx->setPendingException(lastException);
pd->flags |= (shape->enumerable() ? JSPD_ENUMERATE : 0)
| (!shape->writable() ? JSPD_READONLY : 0)
| (!shape->configurable() ? JSPD_PERMANENT : 0);
pd->spare = 0;
pd->alias = JSVAL_VOID;
return JS_TRUE;
}
JS_PUBLIC_API(JSBool)
JS_GetPropertyDescArray(JSContext *cx, JSObject *obj_, JSPropertyDescArray *pda)
{
RootedObject obj(cx, obj_);
assertSameCompartment(cx, obj);
uint32_t i = 0;
JSPropertyDesc *pd = NULL;
if (obj->is<DebugScopeObject>()) {
AutoIdVector props(cx);
if (!Proxy::enumerate(cx, obj, props))
return false;
pd = cx->pod_calloc<JSPropertyDesc>(props.length());
if (!pd)
return false;
for (i = 0; i < props.length(); ++i) {
pd[i].id = JSVAL_NULL;
pd[i].value = JSVAL_NULL;
if (!AddValueRoot(cx, &pd[i].id, NULL))
goto bad;
pd[i].id = IdToValue(props[i]);
if (!AddValueRoot(cx, &pd[i].value, NULL))
goto bad;
if (!Proxy::get(cx, obj, obj, props.handleAt(i), MutableHandleValue::fromMarkedLocation(&pd[i].value)))
goto bad;
}
pda->length = props.length();
pda->array = pd;
return true;
}
Class *clasp;
clasp = obj->getClass();
if (!obj->isNative() || (clasp->flags & JSCLASS_NEW_ENUMERATE)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_CANT_DESCRIBE_PROPS, clasp->name);
return false;
}
if (!clasp->enumerate(cx, obj))
return false;
/* Return an empty pda early if obj has no own properties. */
if (obj->nativeEmpty()) {
pda->length = 0;
pda->array = NULL;
return true;
}
pd = cx->pod_malloc<JSPropertyDesc>(obj->propertyCount());
if (!pd)
return false;
{
Shape::Range<CanGC> r(cx, obj->lastProperty());
RootedShape shape(cx);
for (; !r.empty(); r.popFront()) {
pd[i].id = JSVAL_NULL;
pd[i].value = JSVAL_NULL;
pd[i].alias = JSVAL_NULL;
if (!AddValueRoot(cx, &pd[i].id, NULL))
goto bad;
if (!AddValueRoot(cx, &pd[i].value, NULL))
goto bad;
shape = const_cast<Shape *>(&r.front());
if (!GetPropertyDesc(cx, obj, shape, &pd[i]))
goto bad;
if ((pd[i].flags & JSPD_ALIAS) && !AddValueRoot(cx, &pd[i].alias, NULL))
goto bad;
if (++i == obj->propertyCount())
break;
}
}
pda->length = i;
pda->array = pd;
return true;
bad:
pda->length = i + 1;
pda->array = pd;
JS_PutPropertyDescArray(cx, pda);
return false;
}
JS_PUBLIC_API(void)
JS_PutPropertyDescArray(JSContext *cx, JSPropertyDescArray *pda)
{
JSPropertyDesc *pd;
uint32_t i;
pd = pda->array;
for (i = 0; i < pda->length; i++) {
js_RemoveRoot(cx->runtime(), &pd[i].id);
js_RemoveRoot(cx->runtime(), &pd[i].value);
if (pd[i].flags & JSPD_ALIAS)
js_RemoveRoot(cx->runtime(), &pd[i].alias);
}
js_free(pd);
pda->array = NULL;
pda->length = 0;
}
/************************************************************************/
JS_PUBLIC_API(JSBool)
JS_SetDebuggerHandler(JSRuntime *rt, JSDebuggerHandler handler, void *closure)
{
rt->debugHooks.debuggerHandler = handler;
rt->debugHooks.debuggerHandlerData = closure;
return JS_TRUE;
}
JS_PUBLIC_API(JSBool)
JS_SetSourceHandler(JSRuntime *rt, JSSourceHandler handler, void *closure)
{
rt->debugHooks.sourceHandler = handler;
rt->debugHooks.sourceHandlerData = closure;
return JS_TRUE;
}
JS_PUBLIC_API(JSBool)
JS_SetExecuteHook(JSRuntime *rt, JSInterpreterHook hook, void *closure)
{
rt->debugHooks.executeHook = hook;
rt->debugHooks.executeHookData = closure;
return JS_TRUE;
}
JS_PUBLIC_API(JSBool)
JS_SetCallHook(JSRuntime *rt, JSInterpreterHook hook, void *closure)
{
rt->debugHooks.callHook = hook;
rt->debugHooks.callHookData = closure;
return JS_TRUE;
}
JS_PUBLIC_API(JSBool)
JS_SetThrowHook(JSRuntime *rt, JSThrowHook hook, void *closure)
{
rt->debugHooks.throwHook = hook;
rt->debugHooks.throwHookData = closure;
return JS_TRUE;
}
JS_PUBLIC_API(JSBool)
JS_SetDebugErrorHook(JSRuntime *rt, JSDebugErrorHook hook, void *closure)
{
rt->debugHooks.debugErrorHook = hook;
rt->debugHooks.debugErrorHookData = closure;
return JS_TRUE;
}
/************************************************************************/
JS_PUBLIC_API(const JSDebugHooks *)
JS_GetGlobalDebugHooks(JSRuntime *rt)
{
return &rt->debugHooks;
}
/************************************************************************/
JS_PUBLIC_API(void)
JS_DumpBytecode(JSContext *cx, JSScript *scriptArg)
{
#if defined(DEBUG)
Rooted<JSScript*> script(cx, scriptArg);
Sprinter sprinter(cx);
if (!sprinter.init())
return;
fprintf(stdout, "--- SCRIPT %s:%d ---\n", script->filename(), script->lineno);
js_Disassemble(cx, script, true, &sprinter);
fputs(sprinter.string(), stdout);
fprintf(stdout, "--- END SCRIPT %s:%d ---\n", script->filename(), script->lineno);
#endif
}
extern JS_PUBLIC_API(void)
JS_DumpPCCounts(JSContext *cx, JSScript *scriptArg)
{
#if defined(DEBUG)
Rooted<JSScript*> script(cx, scriptArg);
JS_ASSERT(script->hasScriptCounts);
Sprinter sprinter(cx);
if (!sprinter.init())
return;
fprintf(stdout, "--- SCRIPT %s:%d ---\n", script->filename(), script->lineno);
js_DumpPCCounts(cx, script, &sprinter);
fputs(sprinter.string(), stdout);
fprintf(stdout, "--- END SCRIPT %s:%d ---\n", script->filename(), script->lineno);
#endif
}
namespace {
typedef Vector<JSScript *, 0, SystemAllocPolicy> ScriptsToDump;
static void
DumpBytecodeScriptCallback(JSRuntime *rt, void *data, JSScript *script)
{
static_cast<ScriptsToDump *>(data)->append(script);
}
} /* anonymous namespace */
JS_PUBLIC_API(void)
JS_DumpCompartmentBytecode(JSContext *cx)
{
ScriptsToDump scripts;
IterateScripts(cx->runtime(), cx->compartment(), &scripts, DumpBytecodeScriptCallback);
for (size_t i = 0; i < scripts.length(); i++) {
if (scripts[i]->enclosingScriptsCompiledSuccessfully())
JS_DumpBytecode(cx, scripts[i]);
}
}
JS_PUBLIC_API(void)
JS_DumpCompartmentPCCounts(JSContext *cx)
{
for (CellIter i(cx->zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
JSScript *script = i.get<JSScript>();
if (script->compartment() != cx->compartment())
continue;
if (script->hasScriptCounts && script->enclosingScriptsCompiledSuccessfully())
JS_DumpPCCounts(cx, script);
}
#if defined(JS_ION) && defined(DEBUG)
for (unsigned thingKind = FINALIZE_OBJECT0; thingKind < FINALIZE_OBJECT_LIMIT; thingKind++) {
for (CellIter i(cx->zone(), (AllocKind) thingKind); !i.done(); i.next()) {
JSObject *obj = i.get<JSObject>();
if (obj->compartment() != cx->compartment())
continue;
if (IsAsmJSModuleObject(obj)) {
AsmJSModule &module = AsmJSModuleObjectToModule(obj);
Sprinter sprinter(cx);
if (!sprinter.init())
return;
fprintf(stdout, "--- Asm.js Module ---\n");
for (size_t i = 0; i < module.numFunctionCounts(); i++) {
jit::IonScriptCounts *counts = module.functionCounts(i);
DumpIonScriptCounts(&sprinter, counts);
}
fputs(sprinter.string(), stdout);
fprintf(stdout, "--- END Asm.js Module ---\n");
}
}
}
#endif
}
JS_FRIEND_API(JSBool)
js_CallContextDebugHandler(JSContext *cx)
{
NonBuiltinScriptFrameIter iter(cx);
JS_ASSERT(!iter.done());
RootedValue rval(cx);
RootedScript script(cx, iter.script());
switch (js::CallContextDebugHandler(cx, script, iter.pc(), rval.address())) {
case JSTRAP_ERROR:
JS_ClearPendingException(cx);
return JS_FALSE;
case JSTRAP_THROW:
JS_SetPendingException(cx, rval);
return JS_FALSE;
case JSTRAP_RETURN:
case JSTRAP_CONTINUE:
default:
return JS_TRUE;
}
}
JS_PUBLIC_API(JS::StackDescription *)
JS::DescribeStack(JSContext *cx, unsigned maxFrames)
{
Vector<FrameDescription> frames(cx);
for (NonBuiltinScriptFrameIter i(cx); !i.done(); ++i) {
FrameDescription desc;
desc.script = i.script();
unsigned column = 0;
desc.lineno = PCToLineNumber(i.script(), i.pc(), &column);
desc.fun = i.maybeCallee();
// Don't zero index.
desc.columnno = column + 1;
if (!frames.append(desc))
return NULL;
if (frames.length() == maxFrames)
break;
}
JS::StackDescription *desc = js_new<JS::StackDescription>();
if (!desc)
return NULL;
desc->nframes = frames.length();
desc->frames = frames.extractRawBuffer();
return desc;
}
JS_PUBLIC_API(void)
JS::FreeStackDescription(JSContext *cx, JS::StackDescription *desc)
{
js_delete(desc->frames);
js_delete(desc);
}
class AutoPropertyDescArray
{
JSContext *cx_;
JSPropertyDescArray descArray_;
public:
AutoPropertyDescArray(JSContext *cx)
: cx_(cx)
{
PodZero(&descArray_);
}
~AutoPropertyDescArray()
{
if (descArray_.array)
JS_PutPropertyDescArray(cx_, &descArray_);
}
void fetch(JSObject *obj) {
JS_ASSERT(!descArray_.array);
if (!JS_GetPropertyDescArray(cx_, obj, &descArray_))
descArray_.array = NULL;
}
JSPropertyDescArray * operator ->() {
return &descArray_;
}
};
static const char *
FormatValue(JSContext *cx, const Value &vArg, JSAutoByteString &bytes)
{
RootedValue v(cx, vArg);
JSString *str = ToString<CanGC>(cx, v);
if (!str)
return NULL;
const char *buf = bytes.encodeLatin1(cx, str);
if (!buf)
return NULL;
const char *found = strstr(buf, "function ");
if (found && (found - buf <= 2))
return "[function]";
return buf;
}
static char *
FormatFrame(JSContext *cx, const NonBuiltinScriptFrameIter &iter, char *buf, int num,
JSBool showArgs, JSBool showLocals, JSBool showThisProps)
{
RootedScript script(cx, iter.script());
jsbytecode* pc = iter.pc();
RootedObject scopeChain(cx, iter.scopeChain());
JSAutoCompartment ac(cx, scopeChain);
const char *filename = script->filename();
unsigned lineno = PCToLineNumber(script, pc);
RootedFunction fun(cx, iter.maybeCallee());
RootedString funname(cx);
if (fun)
funname = fun->atom();
RootedObject callObj(cx);
AutoPropertyDescArray callProps(cx);
if (!iter.isJit() && (showArgs || showLocals)) {
JSAbstractFramePtr frame(Jsvalify(iter.abstractFramePtr()));
callObj = frame.callObject(cx);
if (callObj)
callProps.fetch(callObj);
}
RootedValue thisVal(cx);
AutoPropertyDescArray thisProps(cx);
if (iter.computeThis(cx)) {
thisVal = iter.thisv();
if (showThisProps && !thisVal.isPrimitive())
thisProps.fetch(&thisVal.toObject());
}
// print the frame number and function name
if (funname) {
JSAutoByteString funbytes;
buf = JS_sprintf_append(buf, "%d %s(", num, funbytes.encodeLatin1(cx, funname));
} else if (fun) {
buf = JS_sprintf_append(buf, "%d anonymous(", num);
} else {
buf = JS_sprintf_append(buf, "%d <TOP LEVEL>", num);
}
if (!buf)
return buf;
// print the function arguments
if (showArgs && callObj) {
uint32_t namedArgCount = 0;
for (uint32_t i = 0; i < callProps->length; i++) {
JSPropertyDesc* desc = &callProps->array[i];
JSAutoByteString nameBytes;
const char *name = NULL;
if (JSVAL_IS_STRING(desc->id))
name = FormatValue(cx, desc->id, nameBytes);
JSAutoByteString valueBytes;
const char *value = FormatValue(cx, desc->value, valueBytes);
buf = JS_sprintf_append(buf, "%s%s%s%s%s%s",
namedArgCount ? ", " : "",
name ? name :"",
name ? " = " : "",
desc->value.isString() ? "\"" : "",
value ? value : "?unknown?",
desc->value.isString() ? "\"" : "");
if (!buf)
return buf;
namedArgCount++;
}
// print any unnamed trailing args (found in 'arguments' object)
RootedValue val(cx);
if (JS_GetProperty(cx, callObj, "arguments", val.address()) && val.isObject()) {
uint32_t argCount;
RootedObject argsObj(cx, &val.toObject());
if (JS_GetProperty(cx, argsObj, "length", val.address()) &&
ToUint32(cx, val, &argCount) &&
argCount > namedArgCount)
{
for (uint32_t k = namedArgCount; k < argCount; k++) {
char number[8];
JS_snprintf(number, 8, "%d", (int) k);
if (JS_GetProperty(cx, argsObj, number, val.address())) {
JSAutoByteString valueBytes;
const char *value = FormatValue(cx, val, valueBytes);
buf = JS_sprintf_append(buf, "%s%s%s%s",
k ? ", " : "",
val.isString() ? "\"" : "",
value ? value : "?unknown?",
val.isString() ? "\"" : "");
if (!buf)
return buf;
}
}
}
}
}
// print filename and line number
buf = JS_sprintf_append(buf, "%s [\"%s\":%d]\n",
fun ? ")" : "",
filename ? filename : "<unknown>",
lineno);
if (!buf)
return buf;
// print local variables
if (showLocals && callProps->array) {
for (uint32_t i = 0; i < callProps->length; i++) {
JSPropertyDesc* desc = &callProps->array[i];
JSAutoByteString nameBytes;
JSAutoByteString valueBytes;
const char *name = FormatValue(cx, desc->id, nameBytes);
const char *value = FormatValue(cx, desc->value, valueBytes);
if (name && value) {
buf = JS_sprintf_append(buf, " %s = %s%s%s\n",
name,
desc->value.isString() ? "\"" : "",
value,
desc->value.isString() ? "\"" : "");
if (!buf)
return buf;
}
}
}
// print the value of 'this'
if (showLocals) {
if (!thisVal.isUndefined()) {
JSAutoByteString thisValBytes;
RootedString thisValStr(cx, ToString<CanGC>(cx, thisVal));
if (thisValStr) {
if (const char *str = thisValBytes.encodeLatin1(cx, thisValStr)) {
buf = JS_sprintf_append(buf, " this = %s\n", str);
if (!buf)
return buf;
}
}
} else {
buf = JS_sprintf_append(buf, " <failed to get 'this' value>\n");
}
}
// print the properties of 'this', if it is an object
if (showThisProps && thisProps->array) {
for (uint32_t i = 0; i < thisProps->length; i++) {
JSPropertyDesc* desc = &thisProps->array[i];
if (desc->flags & JSPD_ENUMERATE) {
JSAutoByteString nameBytes;
JSAutoByteString valueBytes;
const char *name = FormatValue(cx, desc->id, nameBytes);
const char *value = FormatValue(cx, desc->value, valueBytes);
if (name && value) {
buf = JS_sprintf_append(buf, " this.%s = %s%s%s\n",
name,
desc->value.isString() ? "\"" : "",
value,
desc->value.isString() ? "\"" : "");
if (!buf)
return buf;
}
}
}
}
return buf;
}
JS_PUBLIC_API(char *)
JS::FormatStackDump(JSContext *cx, char *buf,
JSBool showArgs, JSBool showLocals,
JSBool showThisProps)
{
int num = 0;
for (NonBuiltinScriptFrameIter i(cx); !i.done(); ++i) {
buf = FormatFrame(cx, i, buf, num, showArgs, showLocals, showThisProps);
num++;
}
if (!num)
buf = JS_sprintf_append(buf, "JavaScript stack is empty\n");
return buf;
}
JSAbstractFramePtr::JSAbstractFramePtr(void *raw)
: ptr_(uintptr_t(raw))
{ }
JSObject *
JSAbstractFramePtr::scopeChain(JSContext *cx)
{
AbstractFramePtr frame = Valueify(*this);
RootedObject scopeChain(cx, frame.scopeChain());
AutoCompartment ac(cx, scopeChain);
return GetDebugScopeForFrame(cx, frame);
}
JSObject *
JSAbstractFramePtr::callObject(JSContext *cx)
{
AbstractFramePtr frame = Valueify(*this);
if (!frame.isFunctionFrame())
return NULL;
JSObject *o = GetDebugScopeForFrame(cx, frame);
/*
* Given that fp is a function frame and GetDebugScopeForFrame always fills
* in missing scopes, we can expect to find fp's CallObject on 'o'. Note:
* - GetDebugScopeForFrame wraps every ScopeObject (missing or not) with
* a DebugScopeObject proxy.
* - If fp is an eval-in-function, then fp has no callobj of its own and
* JS_GetFrameCallObject will return the innermost function's callobj.
*/
while (o) {
ScopeObject &scope = o->as<DebugScopeObject>().scope();
if (scope.is<CallObject>())
return o;
o = o->enclosingScope();
}
return NULL;
}
JSFunction *
JSAbstractFramePtr::maybeFun()
{
AbstractFramePtr frame = Valueify(*this);
return frame.maybeFun();
}
JSScript *
JSAbstractFramePtr::script()
{
AbstractFramePtr frame = Valueify(*this);
return frame.script();
}
bool
JSAbstractFramePtr::getThisValue(JSContext *cx, MutableHandleValue thisv)
{
AbstractFramePtr frame = Valueify(*this);
RootedObject scopeChain(cx, frame.scopeChain());
js::AutoCompartment ac(cx, scopeChain);
if (!ComputeThis(cx, frame))
return false;
thisv.set(frame.thisValue());
return true;
}
bool
JSAbstractFramePtr::isDebuggerFrame()
{
AbstractFramePtr frame = Valueify(*this);
return frame.isDebuggerFrame();
}
bool
JSAbstractFramePtr::evaluateInStackFrame(JSContext *cx,
const char *bytes, unsigned length,
const char *filename, unsigned lineno,
MutableHandleValue rval)
{
if (!CheckDebugMode(cx))
return false;
size_t len = length;
jschar *chars = InflateString(cx, bytes, &len);
if (!chars)
return false;
length = (unsigned) len;
bool ok = evaluateUCInStackFrame(cx, chars, length, filename, lineno, rval);
js_free(chars);
return ok;
}
bool
JSAbstractFramePtr::evaluateUCInStackFrame(JSContext *cx,
const jschar *chars, unsigned length,
const char *filename, unsigned lineno,
MutableHandleValue rval)
{
if (!CheckDebugMode(cx))
return false;
RootedObject scope(cx, scopeChain(cx));
Rooted<Env*> env(cx, scope);
if (!env)
return false;
AbstractFramePtr frame = Valueify(*this);
if (!ComputeThis(cx, frame))
return false;
RootedValue thisv(cx, frame.thisValue());
js::AutoCompartment ac(cx, env);
return EvaluateInEnv(cx, env, thisv, frame, StableCharPtr(chars, length), length,
filename, lineno, rval);
}
JSBrokenFrameIterator::JSBrokenFrameIterator(JSContext *cx)
{
NonBuiltinScriptFrameIter iter(cx);
data_ = iter.copyData();
}
JSBrokenFrameIterator::~JSBrokenFrameIterator()
{
js_free((ScriptFrameIter::Data *)data_);
}
bool
JSBrokenFrameIterator::done() const
{
NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
return iter.done();
}
JSBrokenFrameIterator &
JSBrokenFrameIterator::operator++()
{
ScriptFrameIter::Data *data = (ScriptFrameIter::Data *)data_;
NonBuiltinScriptFrameIter iter(*data);
++iter;
*data = iter.data_;
return *this;
}
JSAbstractFramePtr
JSBrokenFrameIterator::abstractFramePtr() const
{
NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
return Jsvalify(iter.abstractFramePtr());
}
jsbytecode *
JSBrokenFrameIterator::pc() const
{
NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
return iter.pc();
}
bool
JSBrokenFrameIterator::isConstructing() const
{
NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
return iter.isConstructing();
}