blob: cbc3e85b186d510642e2b801a8164bb98e7462ef [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/. */
#include "tests.h"
#include "jsdbgapi.h"
#include "jscntxt.h"
static int callCount[2] = {0, 0};
static void *
callCountHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, JSBool before,
JSBool *ok, void *closure)
{
callCount[before]++;
JS::RootedValue thisv(cx);
frame.getThisValue(cx, &thisv); // assert if fp is incomplete
return cx; // any non-null value causes the hook to be called again after
}
BEGIN_TEST(testDebugger_bug519719)
{
CHECK(JS_SetDebugMode(cx, JS_TRUE));
JS_SetCallHook(rt, callCountHook, NULL);
EXEC("function call(fn) { fn(0); }\n"
"function f(g) { for (var i = 0; i < 9; i++) call(g); }\n"
"f(Math.sin);\n" // record loop, starting in f
"f(Math.cos);\n"); // side exit in f -> call
CHECK_EQUAL(callCount[0], 20);
CHECK_EQUAL(callCount[1], 20);
return true;
}
END_TEST(testDebugger_bug519719)
static void *
nonStrictThisHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, JSBool before,
JSBool *ok, void *closure)
{
if (before) {
bool *allWrapped = (bool *) closure;
JS::RootedValue thisv(cx);
frame.getThisValue(cx, &thisv);
*allWrapped = *allWrapped && !JSVAL_IS_PRIMITIVE(thisv);
}
return NULL;
}
BEGIN_TEST(testDebugger_getThisNonStrict)
{
bool allWrapped = true;
CHECK(JS_SetDebugMode(cx, JS_TRUE));
JS_SetCallHook(rt, nonStrictThisHook, (void *) &allWrapped);
EXEC("function nonstrict() { }\n"
"Boolean.prototype.nonstrict = nonstrict;\n"
"String.prototype.nonstrict = nonstrict;\n"
"Number.prototype.nonstrict = nonstrict;\n"
"Object.prototype.nonstrict = nonstrict;\n"
"nonstrict.call(true);\n"
"true.nonstrict();\n"
"nonstrict.call('');\n"
"''.nonstrict();\n"
"nonstrict.call(42);\n"
"(42).nonstrict();\n"
// The below don't really get 'wrapped', but it's okay.
"nonstrict.call(undefined);\n"
"nonstrict.call(null);\n"
"nonstrict.call({});\n"
"({}).nonstrict();\n");
CHECK(allWrapped);
return true;
}
END_TEST(testDebugger_getThisNonStrict)
static void *
strictThisHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, JSBool before,
JSBool *ok, void *closure)
{
if (before) {
bool *anyWrapped = (bool *) closure;
JS::RootedValue thisv(cx);
frame.getThisValue(cx, &thisv);
*anyWrapped = *anyWrapped || !JSVAL_IS_PRIMITIVE(thisv);
}
return NULL;
}
BEGIN_TEST(testDebugger_getThisStrict)
{
bool anyWrapped = false;
CHECK(JS_SetDebugMode(cx, JS_TRUE));
JS_SetCallHook(rt, strictThisHook, (void *) &anyWrapped);
EXEC("function strict() { 'use strict'; }\n"
"Boolean.prototype.strict = strict;\n"
"String.prototype.strict = strict;\n"
"Number.prototype.strict = strict;\n"
"strict.call(true);\n"
"true.strict();\n"
"strict.call('');\n"
"''.strict();\n"
"strict.call(42);\n"
"(42).strict();\n"
"strict.call(undefined);\n"
"strict.call(null);\n");
CHECK(!anyWrapped);
return true;
}
END_TEST(testDebugger_getThisStrict)
bool called = false;
static JSTrapStatus
ThrowHook(JSContext *cx, JSScript *, jsbytecode *, jsval *rval, void *closure)
{
JS_ASSERT(!closure);
called = true;
JS::RootedObject global(cx, JS_GetGlobalForScopeChain(cx));
char text[] = "new Error()";
jsval _;
JS_EvaluateScript(cx, global, text, strlen(text), "", 0, &_);
return JSTRAP_CONTINUE;
}
BEGIN_TEST(testDebugger_throwHook)
{
CHECK(JS_SetDebugMode(cx, true));
CHECK(JS_SetThrowHook(rt, ThrowHook, NULL));
EXEC("function foo() { throw 3 };\n"
"for (var i = 0; i < 10; ++i) { \n"
" var x = {}\n"
" try {\n"
" foo(); \n"
" } catch(e) {}\n"
"}\n");
CHECK(called);
CHECK(JS_SetThrowHook(rt, NULL, NULL));
return true;
}
END_TEST(testDebugger_throwHook)
BEGIN_TEST(testDebugger_debuggerObjectVsDebugMode)
{
CHECK(JS_DefineDebuggerObject(cx, global));
JS::RootedObject debuggee(cx, JS_NewGlobalObject(cx, getGlobalClass(), NULL));
CHECK(debuggee);
{
JSAutoCompartment ae(cx, debuggee);
CHECK(JS_SetDebugMode(cx, true));
CHECK(JS_InitStandardClasses(cx, debuggee));
}
JS::RootedObject debuggeeWrapper(cx, debuggee);
CHECK(JS_WrapObject(cx, debuggeeWrapper.address()));
JS::RootedValue v(cx, JS::ObjectValue(*debuggeeWrapper));
CHECK(JS_SetProperty(cx, global, "debuggee", v.address()));
EVAL("var dbg = new Debugger(debuggee);\n"
"var hits = 0;\n"
"dbg.onDebuggerStatement = function () { hits++; };\n"
"debuggee.eval('debugger;');\n"
"hits;\n",
v.address());
CHECK_SAME(v, JSVAL_ONE);
{
JSAutoCompartment ae(cx, debuggee);
CHECK(JS_SetDebugMode(cx, false));
}
EVAL("debuggee.eval('debugger; debugger; debugger;');\n"
"hits;\n",
v.address());
CHECK_SAME(v, INT_TO_JSVAL(4));
return true;
}
END_TEST(testDebugger_debuggerObjectVsDebugMode)
BEGIN_TEST(testDebugger_newScriptHook)
{
// Test that top-level indirect eval fires the newScript hook.
CHECK(JS_DefineDebuggerObject(cx, global));
JS::RootedObject g(cx, JS_NewGlobalObject(cx, getGlobalClass(), NULL));
CHECK(g);
{
JSAutoCompartment ae(cx, g);
CHECK(JS_InitStandardClasses(cx, g));
}
JS::RootedObject gWrapper(cx, g);
CHECK(JS_WrapObject(cx, gWrapper.address()));
JS::RootedValue v(cx, JS::ObjectValue(*gWrapper));
CHECK(JS_SetProperty(cx, global, "g", v.address()));
EXEC("var dbg = Debugger(g);\n"
"var hits = 0;\n"
"dbg.onNewScript = function (s) {\n"
" hits += Number(s instanceof Debugger.Script);\n"
"};\n");
// Since g is a debuggee, g.eval should trigger newScript, regardless of
// what scope object we use to enter the compartment.
//
// Scripts are associated with the global where they're compiled, so we
// deliver them only to debuggers that are watching that particular global.
//
return testIndirectEval(g, "Math.abs(0)");
}
bool testIndirectEval(JS::HandleObject scope, const char *code)
{
EXEC("hits = 0;");
{
JSAutoCompartment ae(cx, scope);
JSString *codestr = JS_NewStringCopyZ(cx, code);
CHECK(codestr);
jsval argv[1] = { STRING_TO_JSVAL(codestr) };
JS::AutoArrayRooter rooter(cx, 1, argv);
jsval v;
CHECK(JS_CallFunctionName(cx, scope, "eval", 1, argv, &v));
}
JS::RootedValue hitsv(cx);
EVAL("hits", hitsv.address());
CHECK_SAME(hitsv, INT_TO_JSVAL(1));
return true;
}
END_TEST(testDebugger_newScriptHook)
BEGIN_TEST(testDebugger_singleStepThrow)
{
CHECK(JS_SetDebugModeForCompartment(cx, cx->compartment(), true));
CHECK(JS_SetInterrupt(rt, onStep, NULL));
CHECK(JS_DefineFunction(cx, global, "setStepMode", setStepMode, 0, 0));
EXEC("var e;\n"
"setStepMode();\n"
"function f() { throw 0; }\n"
"try { f(); }\n"
"catch (x) { e = x; }\n");
return true;
}
static JSBool
setStepMode(JSContext *cx, unsigned argc, jsval *vp)
{
JSScript *script;
JS_DescribeScriptedCaller(cx, &script, NULL);
JS_ASSERT(script);
if (!JS_SetSingleStepMode(cx, script, true))
return false;
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return true;
}
static JSTrapStatus
onStep(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
{
return JSTRAP_CONTINUE;
}
END_TEST(testDebugger_singleStepThrow)