michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jscntxt.h" michael@0: michael@0: #include "js/OldDebugAPI.h" michael@0: #include "jsapi-tests/tests.h" michael@0: michael@0: using namespace js; michael@0: michael@0: static int callCounts[2] = {0, 0}; michael@0: michael@0: static void * michael@0: callCountHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, bool before, michael@0: bool *ok, void *closure) michael@0: { michael@0: callCounts[before]++; michael@0: michael@0: JS::RootedValue thisv(cx); michael@0: frame.getThisValue(cx, &thisv); // assert if fp is incomplete michael@0: michael@0: return cx; // any non-null value causes the hook to be called again after michael@0: } michael@0: michael@0: BEGIN_TEST(testDebugger_bug519719) michael@0: { michael@0: CHECK(JS_SetDebugMode(cx, true)); michael@0: JS_SetCallHook(rt, callCountHook, nullptr); michael@0: EXEC("function call(fn) { fn(0); }\n" michael@0: "function f(g) { for (var i = 0; i < 9; i++) call(g); }\n" michael@0: "f(Math.sin);\n" // record loop, starting in f michael@0: "f(Math.cos);\n"); // side exit in f -> call michael@0: CHECK_EQUAL(callCounts[0], 20); michael@0: CHECK_EQUAL(callCounts[1], 20); michael@0: return true; michael@0: } michael@0: END_TEST(testDebugger_bug519719) michael@0: michael@0: static void * michael@0: nonStrictThisHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, bool before, michael@0: bool *ok, void *closure) michael@0: { michael@0: if (before) { michael@0: bool *allWrapped = (bool *) closure; michael@0: JS::RootedValue thisv(cx); michael@0: frame.getThisValue(cx, &thisv); michael@0: *allWrapped = *allWrapped && !JSVAL_IS_PRIMITIVE(thisv); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: BEGIN_TEST(testDebugger_getThisNonStrict) michael@0: { michael@0: bool allWrapped = true; michael@0: CHECK(JS_SetDebugMode(cx, true)); michael@0: JS_SetCallHook(rt, nonStrictThisHook, (void *) &allWrapped); michael@0: EXEC("function nonstrict() { }\n" michael@0: "Boolean.prototype.nonstrict = nonstrict;\n" michael@0: "String.prototype.nonstrict = nonstrict;\n" michael@0: "Number.prototype.nonstrict = nonstrict;\n" michael@0: "Object.prototype.nonstrict = nonstrict;\n" michael@0: "nonstrict.call(true);\n" michael@0: "true.nonstrict();\n" michael@0: "nonstrict.call('');\n" michael@0: "''.nonstrict();\n" michael@0: "nonstrict.call(42);\n" michael@0: "(42).nonstrict();\n" michael@0: // The below don't really get 'wrapped', but it's okay. michael@0: "nonstrict.call(undefined);\n" michael@0: "nonstrict.call(null);\n" michael@0: "nonstrict.call({});\n" michael@0: "({}).nonstrict();\n"); michael@0: CHECK(allWrapped); michael@0: return true; michael@0: } michael@0: END_TEST(testDebugger_getThisNonStrict) michael@0: michael@0: static void * michael@0: strictThisHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, bool before, michael@0: bool *ok, void *closure) michael@0: { michael@0: if (before) { michael@0: bool *anyWrapped = (bool *) closure; michael@0: JS::RootedValue thisv(cx); michael@0: frame.getThisValue(cx, &thisv); michael@0: *anyWrapped = *anyWrapped || !JSVAL_IS_PRIMITIVE(thisv); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: BEGIN_TEST(testDebugger_getThisStrict) michael@0: { michael@0: bool anyWrapped = false; michael@0: CHECK(JS_SetDebugMode(cx, true)); michael@0: JS_SetCallHook(rt, strictThisHook, (void *) &anyWrapped); michael@0: EXEC("function strict() { 'use strict'; }\n" michael@0: "Boolean.prototype.strict = strict;\n" michael@0: "String.prototype.strict = strict;\n" michael@0: "Number.prototype.strict = strict;\n" michael@0: "strict.call(true);\n" michael@0: "true.strict();\n" michael@0: "strict.call('');\n" michael@0: "''.strict();\n" michael@0: "strict.call(42);\n" michael@0: "(42).strict();\n" michael@0: "strict.call(undefined);\n" michael@0: "strict.call(null);\n"); michael@0: CHECK(!anyWrapped); michael@0: return true; michael@0: } michael@0: END_TEST(testDebugger_getThisStrict) michael@0: michael@0: static bool calledThrowHook = false; michael@0: michael@0: static JSTrapStatus michael@0: ThrowHook(JSContext *cx, JSScript *, jsbytecode *, jsval *rval, void *closure) michael@0: { michael@0: JS_ASSERT(!closure); michael@0: calledThrowHook = true; michael@0: michael@0: JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); michael@0: michael@0: char text[] = "new Error()"; michael@0: JS::RootedValue _(cx); michael@0: JS_EvaluateScript(cx, global, text, strlen(text), "", 0, &_); michael@0: michael@0: return JSTRAP_CONTINUE; michael@0: } michael@0: michael@0: BEGIN_TEST(testDebugger_throwHook) michael@0: { michael@0: CHECK(JS_SetDebugMode(cx, true)); michael@0: CHECK(JS_SetThrowHook(rt, ThrowHook, nullptr)); michael@0: EXEC("function foo() { throw 3 };\n" michael@0: "for (var i = 0; i < 10; ++i) { \n" michael@0: " var x = {}\n" michael@0: " try {\n" michael@0: " foo(); \n" michael@0: " } catch(e) {}\n" michael@0: "}\n"); michael@0: CHECK(calledThrowHook); michael@0: CHECK(JS_SetThrowHook(rt, nullptr, nullptr)); michael@0: return true; michael@0: } michael@0: END_TEST(testDebugger_throwHook) michael@0: michael@0: BEGIN_TEST(testDebugger_debuggerObjectVsDebugMode) michael@0: { michael@0: CHECK(JS_DefineDebuggerObject(cx, global)); michael@0: JS::RootedObject debuggee(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook)); michael@0: CHECK(debuggee); michael@0: michael@0: { michael@0: JSAutoCompartment ae(cx, debuggee); michael@0: CHECK(JS_SetDebugMode(cx, true)); michael@0: CHECK(JS_InitStandardClasses(cx, debuggee)); michael@0: } michael@0: michael@0: JS::RootedObject debuggeeWrapper(cx, debuggee); michael@0: CHECK(JS_WrapObject(cx, &debuggeeWrapper)); michael@0: JS::RootedValue v(cx, JS::ObjectValue(*debuggeeWrapper)); michael@0: CHECK(JS_SetProperty(cx, global, "debuggee", v)); michael@0: michael@0: EVAL("var dbg = new Debugger(debuggee);\n" michael@0: "var hits = 0;\n" michael@0: "dbg.onDebuggerStatement = function () { hits++; };\n" michael@0: "debuggee.eval('debugger;');\n" michael@0: "hits;\n", michael@0: &v); michael@0: CHECK_SAME(v, JSVAL_ONE); michael@0: michael@0: { michael@0: JSAutoCompartment ae(cx, debuggee); michael@0: CHECK(JS_SetDebugMode(cx, false)); michael@0: } michael@0: michael@0: EVAL("debuggee.eval('debugger; debugger; debugger;');\n" michael@0: "hits;\n", michael@0: &v); michael@0: CHECK_SAME(v, INT_TO_JSVAL(4)); michael@0: michael@0: return true; michael@0: } michael@0: END_TEST(testDebugger_debuggerObjectVsDebugMode) michael@0: michael@0: BEGIN_TEST(testDebugger_newScriptHook) michael@0: { michael@0: // Test that top-level indirect eval fires the newScript hook. michael@0: CHECK(JS_DefineDebuggerObject(cx, global)); michael@0: JS::RootedObject g(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook)); michael@0: CHECK(g); michael@0: { michael@0: JSAutoCompartment ae(cx, g); michael@0: CHECK(JS_InitStandardClasses(cx, g)); michael@0: } michael@0: michael@0: JS::RootedObject gWrapper(cx, g); michael@0: CHECK(JS_WrapObject(cx, &gWrapper)); michael@0: JS::RootedValue v(cx, JS::ObjectValue(*gWrapper)); michael@0: CHECK(JS_SetProperty(cx, global, "g", v)); michael@0: michael@0: EXEC("var dbg = Debugger(g);\n" michael@0: "var hits = 0;\n" michael@0: "dbg.onNewScript = function (s) {\n" michael@0: " hits += Number(s instanceof Debugger.Script);\n" michael@0: "};\n"); michael@0: michael@0: // Since g is a debuggee, g.eval should trigger newScript, regardless of michael@0: // what scope object we use to enter the compartment. michael@0: // michael@0: // Scripts are associated with the global where they're compiled, so we michael@0: // deliver them only to debuggers that are watching that particular global. michael@0: // michael@0: return testIndirectEval(g, "Math.abs(0)"); michael@0: } michael@0: michael@0: bool testIndirectEval(JS::HandleObject scope, const char *code) michael@0: { michael@0: EXEC("hits = 0;"); michael@0: michael@0: { michael@0: JSAutoCompartment ae(cx, scope); michael@0: JSString *codestr = JS_NewStringCopyZ(cx, code); michael@0: CHECK(codestr); michael@0: JS::RootedValue arg(cx, JS::StringValue(codestr)); michael@0: JS::RootedValue v(cx); michael@0: CHECK(JS_CallFunctionName(cx, scope, "eval", arg, &v)); michael@0: } michael@0: michael@0: JS::RootedValue hitsv(cx); michael@0: EVAL("hits", &hitsv); michael@0: CHECK_SAME(hitsv, INT_TO_JSVAL(1)); michael@0: return true; michael@0: } michael@0: END_TEST(testDebugger_newScriptHook) michael@0: michael@0: BEGIN_TEST(testDebugger_singleStepThrow) michael@0: { michael@0: CHECK(JS_SetDebugModeForCompartment(cx, cx->compartment(), true)); michael@0: CHECK(JS_SetInterrupt(rt, onStep, nullptr)); michael@0: michael@0: CHECK(JS_DefineFunction(cx, global, "setStepMode", setStepMode, 0, 0)); michael@0: EXEC("var e;\n" michael@0: "setStepMode();\n" michael@0: "function f() { throw 0; }\n" michael@0: "try { f(); }\n" michael@0: "catch (x) { e = x; }\n"); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: setStepMode(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: NonBuiltinScriptFrameIter iter(cx); michael@0: JS::RootedScript script(cx, iter.script()); michael@0: if (!JS_SetSingleStepMode(cx, script, true)) michael@0: return false; michael@0: michael@0: args.rval().set(UndefinedValue()); michael@0: return true; michael@0: } michael@0: michael@0: static JSTrapStatus michael@0: onStep(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure) michael@0: { michael@0: return JSTRAP_CONTINUE; michael@0: } michael@0: END_TEST(testDebugger_singleStepThrow)