1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/jsapi-tests/testDebugger.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,274 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + */ 1.7 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.8 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.9 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.10 + 1.11 +#include "jscntxt.h" 1.12 + 1.13 +#include "js/OldDebugAPI.h" 1.14 +#include "jsapi-tests/tests.h" 1.15 + 1.16 +using namespace js; 1.17 + 1.18 +static int callCounts[2] = {0, 0}; 1.19 + 1.20 +static void * 1.21 +callCountHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, bool before, 1.22 + bool *ok, void *closure) 1.23 +{ 1.24 + callCounts[before]++; 1.25 + 1.26 + JS::RootedValue thisv(cx); 1.27 + frame.getThisValue(cx, &thisv); // assert if fp is incomplete 1.28 + 1.29 + return cx; // any non-null value causes the hook to be called again after 1.30 +} 1.31 + 1.32 +BEGIN_TEST(testDebugger_bug519719) 1.33 +{ 1.34 + CHECK(JS_SetDebugMode(cx, true)); 1.35 + JS_SetCallHook(rt, callCountHook, nullptr); 1.36 + EXEC("function call(fn) { fn(0); }\n" 1.37 + "function f(g) { for (var i = 0; i < 9; i++) call(g); }\n" 1.38 + "f(Math.sin);\n" // record loop, starting in f 1.39 + "f(Math.cos);\n"); // side exit in f -> call 1.40 + CHECK_EQUAL(callCounts[0], 20); 1.41 + CHECK_EQUAL(callCounts[1], 20); 1.42 + return true; 1.43 +} 1.44 +END_TEST(testDebugger_bug519719) 1.45 + 1.46 +static void * 1.47 +nonStrictThisHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, bool before, 1.48 + bool *ok, void *closure) 1.49 +{ 1.50 + if (before) { 1.51 + bool *allWrapped = (bool *) closure; 1.52 + JS::RootedValue thisv(cx); 1.53 + frame.getThisValue(cx, &thisv); 1.54 + *allWrapped = *allWrapped && !JSVAL_IS_PRIMITIVE(thisv); 1.55 + } 1.56 + return nullptr; 1.57 +} 1.58 + 1.59 +BEGIN_TEST(testDebugger_getThisNonStrict) 1.60 +{ 1.61 + bool allWrapped = true; 1.62 + CHECK(JS_SetDebugMode(cx, true)); 1.63 + JS_SetCallHook(rt, nonStrictThisHook, (void *) &allWrapped); 1.64 + EXEC("function nonstrict() { }\n" 1.65 + "Boolean.prototype.nonstrict = nonstrict;\n" 1.66 + "String.prototype.nonstrict = nonstrict;\n" 1.67 + "Number.prototype.nonstrict = nonstrict;\n" 1.68 + "Object.prototype.nonstrict = nonstrict;\n" 1.69 + "nonstrict.call(true);\n" 1.70 + "true.nonstrict();\n" 1.71 + "nonstrict.call('');\n" 1.72 + "''.nonstrict();\n" 1.73 + "nonstrict.call(42);\n" 1.74 + "(42).nonstrict();\n" 1.75 + // The below don't really get 'wrapped', but it's okay. 1.76 + "nonstrict.call(undefined);\n" 1.77 + "nonstrict.call(null);\n" 1.78 + "nonstrict.call({});\n" 1.79 + "({}).nonstrict();\n"); 1.80 + CHECK(allWrapped); 1.81 + return true; 1.82 +} 1.83 +END_TEST(testDebugger_getThisNonStrict) 1.84 + 1.85 +static void * 1.86 +strictThisHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, bool before, 1.87 + bool *ok, void *closure) 1.88 +{ 1.89 + if (before) { 1.90 + bool *anyWrapped = (bool *) closure; 1.91 + JS::RootedValue thisv(cx); 1.92 + frame.getThisValue(cx, &thisv); 1.93 + *anyWrapped = *anyWrapped || !JSVAL_IS_PRIMITIVE(thisv); 1.94 + } 1.95 + return nullptr; 1.96 +} 1.97 + 1.98 +BEGIN_TEST(testDebugger_getThisStrict) 1.99 +{ 1.100 + bool anyWrapped = false; 1.101 + CHECK(JS_SetDebugMode(cx, true)); 1.102 + JS_SetCallHook(rt, strictThisHook, (void *) &anyWrapped); 1.103 + EXEC("function strict() { 'use strict'; }\n" 1.104 + "Boolean.prototype.strict = strict;\n" 1.105 + "String.prototype.strict = strict;\n" 1.106 + "Number.prototype.strict = strict;\n" 1.107 + "strict.call(true);\n" 1.108 + "true.strict();\n" 1.109 + "strict.call('');\n" 1.110 + "''.strict();\n" 1.111 + "strict.call(42);\n" 1.112 + "(42).strict();\n" 1.113 + "strict.call(undefined);\n" 1.114 + "strict.call(null);\n"); 1.115 + CHECK(!anyWrapped); 1.116 + return true; 1.117 +} 1.118 +END_TEST(testDebugger_getThisStrict) 1.119 + 1.120 +static bool calledThrowHook = false; 1.121 + 1.122 +static JSTrapStatus 1.123 +ThrowHook(JSContext *cx, JSScript *, jsbytecode *, jsval *rval, void *closure) 1.124 +{ 1.125 + JS_ASSERT(!closure); 1.126 + calledThrowHook = true; 1.127 + 1.128 + JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); 1.129 + 1.130 + char text[] = "new Error()"; 1.131 + JS::RootedValue _(cx); 1.132 + JS_EvaluateScript(cx, global, text, strlen(text), "", 0, &_); 1.133 + 1.134 + return JSTRAP_CONTINUE; 1.135 +} 1.136 + 1.137 +BEGIN_TEST(testDebugger_throwHook) 1.138 +{ 1.139 + CHECK(JS_SetDebugMode(cx, true)); 1.140 + CHECK(JS_SetThrowHook(rt, ThrowHook, nullptr)); 1.141 + EXEC("function foo() { throw 3 };\n" 1.142 + "for (var i = 0; i < 10; ++i) { \n" 1.143 + " var x = {}\n" 1.144 + " try {\n" 1.145 + " foo(); \n" 1.146 + " } catch(e) {}\n" 1.147 + "}\n"); 1.148 + CHECK(calledThrowHook); 1.149 + CHECK(JS_SetThrowHook(rt, nullptr, nullptr)); 1.150 + return true; 1.151 +} 1.152 +END_TEST(testDebugger_throwHook) 1.153 + 1.154 +BEGIN_TEST(testDebugger_debuggerObjectVsDebugMode) 1.155 +{ 1.156 + CHECK(JS_DefineDebuggerObject(cx, global)); 1.157 + JS::RootedObject debuggee(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook)); 1.158 + CHECK(debuggee); 1.159 + 1.160 + { 1.161 + JSAutoCompartment ae(cx, debuggee); 1.162 + CHECK(JS_SetDebugMode(cx, true)); 1.163 + CHECK(JS_InitStandardClasses(cx, debuggee)); 1.164 + } 1.165 + 1.166 + JS::RootedObject debuggeeWrapper(cx, debuggee); 1.167 + CHECK(JS_WrapObject(cx, &debuggeeWrapper)); 1.168 + JS::RootedValue v(cx, JS::ObjectValue(*debuggeeWrapper)); 1.169 + CHECK(JS_SetProperty(cx, global, "debuggee", v)); 1.170 + 1.171 + EVAL("var dbg = new Debugger(debuggee);\n" 1.172 + "var hits = 0;\n" 1.173 + "dbg.onDebuggerStatement = function () { hits++; };\n" 1.174 + "debuggee.eval('debugger;');\n" 1.175 + "hits;\n", 1.176 + &v); 1.177 + CHECK_SAME(v, JSVAL_ONE); 1.178 + 1.179 + { 1.180 + JSAutoCompartment ae(cx, debuggee); 1.181 + CHECK(JS_SetDebugMode(cx, false)); 1.182 + } 1.183 + 1.184 + EVAL("debuggee.eval('debugger; debugger; debugger;');\n" 1.185 + "hits;\n", 1.186 + &v); 1.187 + CHECK_SAME(v, INT_TO_JSVAL(4)); 1.188 + 1.189 + return true; 1.190 +} 1.191 +END_TEST(testDebugger_debuggerObjectVsDebugMode) 1.192 + 1.193 +BEGIN_TEST(testDebugger_newScriptHook) 1.194 +{ 1.195 + // Test that top-level indirect eval fires the newScript hook. 1.196 + CHECK(JS_DefineDebuggerObject(cx, global)); 1.197 + JS::RootedObject g(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook)); 1.198 + CHECK(g); 1.199 + { 1.200 + JSAutoCompartment ae(cx, g); 1.201 + CHECK(JS_InitStandardClasses(cx, g)); 1.202 + } 1.203 + 1.204 + JS::RootedObject gWrapper(cx, g); 1.205 + CHECK(JS_WrapObject(cx, &gWrapper)); 1.206 + JS::RootedValue v(cx, JS::ObjectValue(*gWrapper)); 1.207 + CHECK(JS_SetProperty(cx, global, "g", v)); 1.208 + 1.209 + EXEC("var dbg = Debugger(g);\n" 1.210 + "var hits = 0;\n" 1.211 + "dbg.onNewScript = function (s) {\n" 1.212 + " hits += Number(s instanceof Debugger.Script);\n" 1.213 + "};\n"); 1.214 + 1.215 + // Since g is a debuggee, g.eval should trigger newScript, regardless of 1.216 + // what scope object we use to enter the compartment. 1.217 + // 1.218 + // Scripts are associated with the global where they're compiled, so we 1.219 + // deliver them only to debuggers that are watching that particular global. 1.220 + // 1.221 + return testIndirectEval(g, "Math.abs(0)"); 1.222 +} 1.223 + 1.224 +bool testIndirectEval(JS::HandleObject scope, const char *code) 1.225 +{ 1.226 + EXEC("hits = 0;"); 1.227 + 1.228 + { 1.229 + JSAutoCompartment ae(cx, scope); 1.230 + JSString *codestr = JS_NewStringCopyZ(cx, code); 1.231 + CHECK(codestr); 1.232 + JS::RootedValue arg(cx, JS::StringValue(codestr)); 1.233 + JS::RootedValue v(cx); 1.234 + CHECK(JS_CallFunctionName(cx, scope, "eval", arg, &v)); 1.235 + } 1.236 + 1.237 + JS::RootedValue hitsv(cx); 1.238 + EVAL("hits", &hitsv); 1.239 + CHECK_SAME(hitsv, INT_TO_JSVAL(1)); 1.240 + return true; 1.241 +} 1.242 +END_TEST(testDebugger_newScriptHook) 1.243 + 1.244 +BEGIN_TEST(testDebugger_singleStepThrow) 1.245 + { 1.246 + CHECK(JS_SetDebugModeForCompartment(cx, cx->compartment(), true)); 1.247 + CHECK(JS_SetInterrupt(rt, onStep, nullptr)); 1.248 + 1.249 + CHECK(JS_DefineFunction(cx, global, "setStepMode", setStepMode, 0, 0)); 1.250 + EXEC("var e;\n" 1.251 + "setStepMode();\n" 1.252 + "function f() { throw 0; }\n" 1.253 + "try { f(); }\n" 1.254 + "catch (x) { e = x; }\n"); 1.255 + return true; 1.256 + } 1.257 + 1.258 + static bool 1.259 + setStepMode(JSContext *cx, unsigned argc, jsval *vp) 1.260 + { 1.261 + CallArgs args = CallArgsFromVp(argc, vp); 1.262 + 1.263 + NonBuiltinScriptFrameIter iter(cx); 1.264 + JS::RootedScript script(cx, iter.script()); 1.265 + if (!JS_SetSingleStepMode(cx, script, true)) 1.266 + return false; 1.267 + 1.268 + args.rval().set(UndefinedValue()); 1.269 + return true; 1.270 + } 1.271 + 1.272 + static JSTrapStatus 1.273 + onStep(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure) 1.274 + { 1.275 + return JSTRAP_CONTINUE; 1.276 + } 1.277 +END_TEST(testDebugger_singleStepThrow)