|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 */ |
|
4 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
5 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
7 |
|
8 #include "jscntxt.h" |
|
9 |
|
10 #include "js/OldDebugAPI.h" |
|
11 #include "jsapi-tests/tests.h" |
|
12 |
|
13 using namespace js; |
|
14 |
|
15 static int callCounts[2] = {0, 0}; |
|
16 |
|
17 static void * |
|
18 callCountHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, bool before, |
|
19 bool *ok, void *closure) |
|
20 { |
|
21 callCounts[before]++; |
|
22 |
|
23 JS::RootedValue thisv(cx); |
|
24 frame.getThisValue(cx, &thisv); // assert if fp is incomplete |
|
25 |
|
26 return cx; // any non-null value causes the hook to be called again after |
|
27 } |
|
28 |
|
29 BEGIN_TEST(testDebugger_bug519719) |
|
30 { |
|
31 CHECK(JS_SetDebugMode(cx, true)); |
|
32 JS_SetCallHook(rt, callCountHook, nullptr); |
|
33 EXEC("function call(fn) { fn(0); }\n" |
|
34 "function f(g) { for (var i = 0; i < 9; i++) call(g); }\n" |
|
35 "f(Math.sin);\n" // record loop, starting in f |
|
36 "f(Math.cos);\n"); // side exit in f -> call |
|
37 CHECK_EQUAL(callCounts[0], 20); |
|
38 CHECK_EQUAL(callCounts[1], 20); |
|
39 return true; |
|
40 } |
|
41 END_TEST(testDebugger_bug519719) |
|
42 |
|
43 static void * |
|
44 nonStrictThisHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, bool before, |
|
45 bool *ok, void *closure) |
|
46 { |
|
47 if (before) { |
|
48 bool *allWrapped = (bool *) closure; |
|
49 JS::RootedValue thisv(cx); |
|
50 frame.getThisValue(cx, &thisv); |
|
51 *allWrapped = *allWrapped && !JSVAL_IS_PRIMITIVE(thisv); |
|
52 } |
|
53 return nullptr; |
|
54 } |
|
55 |
|
56 BEGIN_TEST(testDebugger_getThisNonStrict) |
|
57 { |
|
58 bool allWrapped = true; |
|
59 CHECK(JS_SetDebugMode(cx, true)); |
|
60 JS_SetCallHook(rt, nonStrictThisHook, (void *) &allWrapped); |
|
61 EXEC("function nonstrict() { }\n" |
|
62 "Boolean.prototype.nonstrict = nonstrict;\n" |
|
63 "String.prototype.nonstrict = nonstrict;\n" |
|
64 "Number.prototype.nonstrict = nonstrict;\n" |
|
65 "Object.prototype.nonstrict = nonstrict;\n" |
|
66 "nonstrict.call(true);\n" |
|
67 "true.nonstrict();\n" |
|
68 "nonstrict.call('');\n" |
|
69 "''.nonstrict();\n" |
|
70 "nonstrict.call(42);\n" |
|
71 "(42).nonstrict();\n" |
|
72 // The below don't really get 'wrapped', but it's okay. |
|
73 "nonstrict.call(undefined);\n" |
|
74 "nonstrict.call(null);\n" |
|
75 "nonstrict.call({});\n" |
|
76 "({}).nonstrict();\n"); |
|
77 CHECK(allWrapped); |
|
78 return true; |
|
79 } |
|
80 END_TEST(testDebugger_getThisNonStrict) |
|
81 |
|
82 static void * |
|
83 strictThisHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, bool before, |
|
84 bool *ok, void *closure) |
|
85 { |
|
86 if (before) { |
|
87 bool *anyWrapped = (bool *) closure; |
|
88 JS::RootedValue thisv(cx); |
|
89 frame.getThisValue(cx, &thisv); |
|
90 *anyWrapped = *anyWrapped || !JSVAL_IS_PRIMITIVE(thisv); |
|
91 } |
|
92 return nullptr; |
|
93 } |
|
94 |
|
95 BEGIN_TEST(testDebugger_getThisStrict) |
|
96 { |
|
97 bool anyWrapped = false; |
|
98 CHECK(JS_SetDebugMode(cx, true)); |
|
99 JS_SetCallHook(rt, strictThisHook, (void *) &anyWrapped); |
|
100 EXEC("function strict() { 'use strict'; }\n" |
|
101 "Boolean.prototype.strict = strict;\n" |
|
102 "String.prototype.strict = strict;\n" |
|
103 "Number.prototype.strict = strict;\n" |
|
104 "strict.call(true);\n" |
|
105 "true.strict();\n" |
|
106 "strict.call('');\n" |
|
107 "''.strict();\n" |
|
108 "strict.call(42);\n" |
|
109 "(42).strict();\n" |
|
110 "strict.call(undefined);\n" |
|
111 "strict.call(null);\n"); |
|
112 CHECK(!anyWrapped); |
|
113 return true; |
|
114 } |
|
115 END_TEST(testDebugger_getThisStrict) |
|
116 |
|
117 static bool calledThrowHook = false; |
|
118 |
|
119 static JSTrapStatus |
|
120 ThrowHook(JSContext *cx, JSScript *, jsbytecode *, jsval *rval, void *closure) |
|
121 { |
|
122 JS_ASSERT(!closure); |
|
123 calledThrowHook = true; |
|
124 |
|
125 JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); |
|
126 |
|
127 char text[] = "new Error()"; |
|
128 JS::RootedValue _(cx); |
|
129 JS_EvaluateScript(cx, global, text, strlen(text), "", 0, &_); |
|
130 |
|
131 return JSTRAP_CONTINUE; |
|
132 } |
|
133 |
|
134 BEGIN_TEST(testDebugger_throwHook) |
|
135 { |
|
136 CHECK(JS_SetDebugMode(cx, true)); |
|
137 CHECK(JS_SetThrowHook(rt, ThrowHook, nullptr)); |
|
138 EXEC("function foo() { throw 3 };\n" |
|
139 "for (var i = 0; i < 10; ++i) { \n" |
|
140 " var x = {}\n" |
|
141 " try {\n" |
|
142 " foo(); \n" |
|
143 " } catch(e) {}\n" |
|
144 "}\n"); |
|
145 CHECK(calledThrowHook); |
|
146 CHECK(JS_SetThrowHook(rt, nullptr, nullptr)); |
|
147 return true; |
|
148 } |
|
149 END_TEST(testDebugger_throwHook) |
|
150 |
|
151 BEGIN_TEST(testDebugger_debuggerObjectVsDebugMode) |
|
152 { |
|
153 CHECK(JS_DefineDebuggerObject(cx, global)); |
|
154 JS::RootedObject debuggee(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook)); |
|
155 CHECK(debuggee); |
|
156 |
|
157 { |
|
158 JSAutoCompartment ae(cx, debuggee); |
|
159 CHECK(JS_SetDebugMode(cx, true)); |
|
160 CHECK(JS_InitStandardClasses(cx, debuggee)); |
|
161 } |
|
162 |
|
163 JS::RootedObject debuggeeWrapper(cx, debuggee); |
|
164 CHECK(JS_WrapObject(cx, &debuggeeWrapper)); |
|
165 JS::RootedValue v(cx, JS::ObjectValue(*debuggeeWrapper)); |
|
166 CHECK(JS_SetProperty(cx, global, "debuggee", v)); |
|
167 |
|
168 EVAL("var dbg = new Debugger(debuggee);\n" |
|
169 "var hits = 0;\n" |
|
170 "dbg.onDebuggerStatement = function () { hits++; };\n" |
|
171 "debuggee.eval('debugger;');\n" |
|
172 "hits;\n", |
|
173 &v); |
|
174 CHECK_SAME(v, JSVAL_ONE); |
|
175 |
|
176 { |
|
177 JSAutoCompartment ae(cx, debuggee); |
|
178 CHECK(JS_SetDebugMode(cx, false)); |
|
179 } |
|
180 |
|
181 EVAL("debuggee.eval('debugger; debugger; debugger;');\n" |
|
182 "hits;\n", |
|
183 &v); |
|
184 CHECK_SAME(v, INT_TO_JSVAL(4)); |
|
185 |
|
186 return true; |
|
187 } |
|
188 END_TEST(testDebugger_debuggerObjectVsDebugMode) |
|
189 |
|
190 BEGIN_TEST(testDebugger_newScriptHook) |
|
191 { |
|
192 // Test that top-level indirect eval fires the newScript hook. |
|
193 CHECK(JS_DefineDebuggerObject(cx, global)); |
|
194 JS::RootedObject g(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook)); |
|
195 CHECK(g); |
|
196 { |
|
197 JSAutoCompartment ae(cx, g); |
|
198 CHECK(JS_InitStandardClasses(cx, g)); |
|
199 } |
|
200 |
|
201 JS::RootedObject gWrapper(cx, g); |
|
202 CHECK(JS_WrapObject(cx, &gWrapper)); |
|
203 JS::RootedValue v(cx, JS::ObjectValue(*gWrapper)); |
|
204 CHECK(JS_SetProperty(cx, global, "g", v)); |
|
205 |
|
206 EXEC("var dbg = Debugger(g);\n" |
|
207 "var hits = 0;\n" |
|
208 "dbg.onNewScript = function (s) {\n" |
|
209 " hits += Number(s instanceof Debugger.Script);\n" |
|
210 "};\n"); |
|
211 |
|
212 // Since g is a debuggee, g.eval should trigger newScript, regardless of |
|
213 // what scope object we use to enter the compartment. |
|
214 // |
|
215 // Scripts are associated with the global where they're compiled, so we |
|
216 // deliver them only to debuggers that are watching that particular global. |
|
217 // |
|
218 return testIndirectEval(g, "Math.abs(0)"); |
|
219 } |
|
220 |
|
221 bool testIndirectEval(JS::HandleObject scope, const char *code) |
|
222 { |
|
223 EXEC("hits = 0;"); |
|
224 |
|
225 { |
|
226 JSAutoCompartment ae(cx, scope); |
|
227 JSString *codestr = JS_NewStringCopyZ(cx, code); |
|
228 CHECK(codestr); |
|
229 JS::RootedValue arg(cx, JS::StringValue(codestr)); |
|
230 JS::RootedValue v(cx); |
|
231 CHECK(JS_CallFunctionName(cx, scope, "eval", arg, &v)); |
|
232 } |
|
233 |
|
234 JS::RootedValue hitsv(cx); |
|
235 EVAL("hits", &hitsv); |
|
236 CHECK_SAME(hitsv, INT_TO_JSVAL(1)); |
|
237 return true; |
|
238 } |
|
239 END_TEST(testDebugger_newScriptHook) |
|
240 |
|
241 BEGIN_TEST(testDebugger_singleStepThrow) |
|
242 { |
|
243 CHECK(JS_SetDebugModeForCompartment(cx, cx->compartment(), true)); |
|
244 CHECK(JS_SetInterrupt(rt, onStep, nullptr)); |
|
245 |
|
246 CHECK(JS_DefineFunction(cx, global, "setStepMode", setStepMode, 0, 0)); |
|
247 EXEC("var e;\n" |
|
248 "setStepMode();\n" |
|
249 "function f() { throw 0; }\n" |
|
250 "try { f(); }\n" |
|
251 "catch (x) { e = x; }\n"); |
|
252 return true; |
|
253 } |
|
254 |
|
255 static bool |
|
256 setStepMode(JSContext *cx, unsigned argc, jsval *vp) |
|
257 { |
|
258 CallArgs args = CallArgsFromVp(argc, vp); |
|
259 |
|
260 NonBuiltinScriptFrameIter iter(cx); |
|
261 JS::RootedScript script(cx, iter.script()); |
|
262 if (!JS_SetSingleStepMode(cx, script, true)) |
|
263 return false; |
|
264 |
|
265 args.rval().set(UndefinedValue()); |
|
266 return true; |
|
267 } |
|
268 |
|
269 static JSTrapStatus |
|
270 onStep(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure) |
|
271 { |
|
272 return JSTRAP_CONTINUE; |
|
273 } |
|
274 END_TEST(testDebugger_singleStepThrow) |