/* -*- 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)