Bug 1832850 - Part 5: Move the allocateObject definition into gc/Nursery.h r=jandem
[gecko.git] / js / src / jsapi-tests / testSavedStacks.cpp
blobe99746ec2f49d6a1d7933dd40a8f7efc1d3b515f
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/Utf8.h" // mozilla::Utf8Unit
9 #include "builtin/TestingFunctions.h"
10 #include "js/CompilationAndEvaluation.h" // JS::Evaluate
11 #include "js/Exception.h"
12 #include "js/SavedFrameAPI.h"
13 #include "js/SourceText.h" // JS::Source{Ownership,Text}
14 #include "js/Stack.h"
15 #include "jsapi-tests/tests.h"
16 #include "util/Text.h"
17 #include "vm/ArrayObject.h"
18 #include "vm/Realm.h"
19 #include "vm/SavedStacks.h"
21 BEGIN_TEST(testSavedStacks_withNoStack) {
22 JS::Realm* realm = cx->realm();
23 realm->setAllocationMetadataBuilder(&js::SavedStacks::metadataBuilder);
24 JS::RootedObject obj(cx, js::NewDenseEmptyArray(cx));
25 realm->setAllocationMetadataBuilder(nullptr);
26 return true;
28 END_TEST(testSavedStacks_withNoStack)
30 BEGIN_TEST(testSavedStacks_ApiDefaultValues) {
31 JS::Rooted<js::SavedFrame*> savedFrame(cx, nullptr);
33 JSPrincipals* principals = cx->realm()->principals();
35 // Source
36 JS::RootedString str(cx);
37 JS::SavedFrameResult result =
38 JS::GetSavedFrameSource(cx, principals, savedFrame, &str);
39 CHECK(result == JS::SavedFrameResult::AccessDenied);
40 CHECK(str.get() == cx->runtime()->emptyString);
42 // Line
43 uint32_t line = 123;
44 result = JS::GetSavedFrameLine(cx, principals, savedFrame, &line);
45 CHECK(result == JS::SavedFrameResult::AccessDenied);
46 CHECK(line == 0);
48 // Column
49 uint32_t column = 123;
50 result = JS::GetSavedFrameColumn(cx, principals, savedFrame, &column);
51 CHECK(result == JS::SavedFrameResult::AccessDenied);
52 CHECK(column == 0);
54 // Function display name
55 result =
56 JS::GetSavedFrameFunctionDisplayName(cx, principals, savedFrame, &str);
57 CHECK(result == JS::SavedFrameResult::AccessDenied);
58 CHECK(str.get() == nullptr);
60 // Parent
61 JS::RootedObject parent(cx);
62 result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent);
63 CHECK(result == JS::SavedFrameResult::AccessDenied);
64 CHECK(parent.get() == nullptr);
66 // Stack string
67 CHECK(JS::BuildStackString(cx, principals, savedFrame, &str));
68 CHECK(str.get() == cx->runtime()->emptyString);
70 return true;
72 END_TEST(testSavedStacks_ApiDefaultValues)
74 BEGIN_TEST(testSavedStacks_RangeBasedForLoops) {
75 CHECK(js::DefineTestingFunctions(cx, global, false, false));
77 JS::RootedValue val(cx);
78 CHECK(
79 evaluate("(function one() { \n" // 1
80 " return (function two() { \n" // 2
81 " return (function three() { \n" // 3
82 " return saveStack(); \n" // 4
83 " }()); \n" // 5
84 " }()); \n" // 6
85 "}()); \n", // 7
86 "filename.js", 1, &val));
88 CHECK(val.isObject());
89 JS::RootedObject obj(cx, &val.toObject());
91 CHECK(obj->is<js::SavedFrame>());
92 JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
94 JS::Rooted<js::SavedFrame*> rf(cx, savedFrame);
95 for (JS::Handle<js::SavedFrame*> frame :
96 js::SavedFrame::RootedRange(cx, rf)) {
97 JS_GC(cx);
98 CHECK(frame == rf);
99 rf = rf->getParent();
101 CHECK(rf == nullptr);
103 // Stack string
104 static const char SpiderMonkeyStack[] =
105 "three@filename.js:4:14\n"
106 "two@filename.js:5:6\n"
107 "one@filename.js:6:4\n"
108 "@filename.js:7:2\n";
109 static const char V8Stack[] =
110 " at three (filename.js:4:14)\n"
111 " at two (filename.js:5:6)\n"
112 " at one (filename.js:6:4)\n"
113 " at filename.js:7:2";
114 struct {
115 js::StackFormat format;
116 const char* expected;
117 } expectations[] = {{js::StackFormat::Default, SpiderMonkeyStack},
118 {js::StackFormat::SpiderMonkey, SpiderMonkeyStack},
119 {js::StackFormat::V8, V8Stack}};
120 auto CheckStacks = [&]() {
121 for (auto& expectation : expectations) {
122 JS::RootedString str(cx);
123 JSPrincipals* principals = cx->realm()->principals();
124 CHECK(JS::BuildStackString(cx, principals, savedFrame, &str, 0,
125 expectation.format));
126 JSLinearString* lin = str->ensureLinear(cx);
127 CHECK(lin);
128 CHECK(js::StringEqualsAscii(lin, expectation.expected));
130 return true;
133 CHECK(CheckStacks());
135 js::SetStackFormat(cx, js::StackFormat::V8);
136 expectations[0].expected = V8Stack;
138 CHECK(CheckStacks());
140 return true;
142 END_TEST(testSavedStacks_RangeBasedForLoops)
144 BEGIN_TEST(testSavedStacks_ErrorStackSpiderMonkey) {
145 JS::RootedValue val(cx);
146 CHECK(
147 evaluate("(function one() { \n" // 1
148 " return (function two() { \n" // 2
149 " return (function three() { \n" // 3
150 " return new Error('foo'); \n" // 4
151 " }()); \n" // 5
152 " }()); \n" // 6
153 "}()).stack \n", // 7
154 "filename.js", 1, &val));
156 CHECK(val.isString());
157 JS::RootedString stack(cx, val.toString());
159 // Stack string
160 static const char SpiderMonkeyStack[] =
161 "three@filename.js:4:14\n"
162 "two@filename.js:5:6\n"
163 "one@filename.js:6:4\n"
164 "@filename.js:7:2\n";
165 JSLinearString* lin = stack->ensureLinear(cx);
166 CHECK(lin);
167 CHECK(js::StringEqualsLiteral(lin, SpiderMonkeyStack));
169 return true;
171 END_TEST(testSavedStacks_ErrorStackSpiderMonkey)
173 BEGIN_TEST(testSavedStacks_ErrorStackV8) {
174 js::SetStackFormat(cx, js::StackFormat::V8);
176 JS::RootedValue val(cx);
177 CHECK(
178 evaluate("(function one() { \n" // 1
179 " return (function two() { \n" // 2
180 " return (function three() { \n" // 3
181 " return new Error('foo'); \n" // 4
182 " }()); \n" // 5
183 " }()); \n" // 6
184 "}()).stack \n", // 7
185 "filename.js", 1, &val));
187 CHECK(val.isString());
188 JS::RootedString stack(cx, val.toString());
190 // Stack string
191 static const char V8Stack[] =
192 "Error: foo\n"
193 " at three (filename.js:4:14)\n"
194 " at two (filename.js:5:6)\n"
195 " at one (filename.js:6:4)\n"
196 " at filename.js:7:2";
197 JSLinearString* lin = stack->ensureLinear(cx);
198 CHECK(lin);
199 CHECK(js::StringEqualsLiteral(lin, V8Stack));
201 return true;
203 END_TEST(testSavedStacks_ErrorStackV8)
205 BEGIN_TEST(testSavedStacks_selfHostedFrames) {
206 CHECK(js::DefineTestingFunctions(cx, global, false, false));
208 JS::RootedValue val(cx);
209 // 0 1 2 3
210 // 0123456789012345678901234567890123456789
211 CHECK(
212 evaluate("(function one() { \n" // 1
213 " try { \n" // 2
214 " [1].map(function two() { \n" // 3
215 " throw saveStack(); \n" // 4
216 " }); \n" // 5
217 " } catch (stack) { \n" // 6
218 " return stack; \n" // 7
219 " } \n" // 8
220 "}()) \n", // 9
221 "filename.js", 1, &val));
223 CHECK(val.isObject());
224 JS::RootedObject obj(cx, &val.toObject());
226 CHECK(obj->is<js::SavedFrame>());
227 JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
229 JS::Rooted<js::SavedFrame*> selfHostedFrame(cx, savedFrame->getParent());
230 CHECK(selfHostedFrame->isSelfHosted(cx));
232 JSPrincipals* principals = cx->realm()->principals();
234 // Source
235 JS::RootedString str(cx);
236 JS::SavedFrameResult result = JS::GetSavedFrameSource(
237 cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude);
238 CHECK(result == JS::SavedFrameResult::Ok);
239 JSLinearString* lin = str->ensureLinear(cx);
240 CHECK(lin);
241 CHECK(js::StringEqualsLiteral(lin, "filename.js"));
243 // Source, including self-hosted frames
244 result = JS::GetSavedFrameSource(cx, principals, selfHostedFrame, &str,
245 JS::SavedFrameSelfHosted::Include);
246 CHECK(result == JS::SavedFrameResult::Ok);
247 lin = str->ensureLinear(cx);
248 CHECK(lin);
249 CHECK(js::StringEqualsLiteral(lin, "self-hosted"));
251 // Line
252 uint32_t line = 123;
253 result = JS::GetSavedFrameLine(cx, principals, selfHostedFrame, &line,
254 JS::SavedFrameSelfHosted::Exclude);
255 CHECK(result == JS::SavedFrameResult::Ok);
256 CHECK_EQUAL(line, 3U);
258 // Column
259 uint32_t column = 123;
260 result = JS::GetSavedFrameColumn(cx, principals, selfHostedFrame, &column,
261 JS::SavedFrameSelfHosted::Exclude);
262 CHECK(result == JS::SavedFrameResult::Ok);
263 CHECK_EQUAL(column, 9U);
265 // Function display name
266 result = JS::GetSavedFrameFunctionDisplayName(
267 cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude);
268 CHECK(result == JS::SavedFrameResult::Ok);
269 lin = str->ensureLinear(cx);
270 CHECK(lin);
271 CHECK(js::StringEqualsLiteral(lin, "one"));
273 // Parent
274 JS::RootedObject parent(cx);
275 result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent,
276 JS::SavedFrameSelfHosted::Exclude);
277 CHECK(result == JS::SavedFrameResult::Ok);
278 // JS::GetSavedFrameParent does this super funky and potentially unexpected
279 // thing where it doesn't return the next subsumed parent but any next
280 // parent. This so that callers can still get the "asyncParent" property
281 // which is only on the first frame of the async parent stack and that frame
282 // might not be subsumed by the caller. It is expected that callers will
283 // still interact with the frame through the JSAPI accessors, so this should
284 // be safe and should not leak privileged info to unprivileged
285 // callers. However, because of that, we don't test that the parent we get
286 // here is the selfHostedFrame's parent (because, as just explained, it
287 // isn't) and instead check that asking for the source property gives us the
288 // expected value.
289 result = JS::GetSavedFrameSource(cx, principals, parent, &str,
290 JS::SavedFrameSelfHosted::Exclude);
291 CHECK(result == JS::SavedFrameResult::Ok);
292 lin = str->ensureLinear(cx);
293 CHECK(lin);
294 CHECK(js::StringEqualsLiteral(lin, "filename.js"));
296 return true;
298 END_TEST(testSavedStacks_selfHostedFrames)
300 BEGIN_TEST(test_GetPendingExceptionStack) {
301 CHECK(js::DefineTestingFunctions(cx, global, false, false));
303 JSPrincipals* principals = cx->realm()->principals();
305 static const char sourceText[] =
306 // 1 2 3
307 // 123456789012345678901234567890123456789
308 "(function one() { \n" // 1
309 " (function two() { \n" // 2
310 " (function three() { \n" // 3
311 " throw 5; \n" // 4
312 " }()); \n" // 5
313 " }()); \n" // 6
314 "}()) \n"; // 7
316 JS::CompileOptions opts(cx);
317 opts.setFileAndLine("filename.js", 1U);
319 JS::SourceText<mozilla::Utf8Unit> srcBuf;
320 CHECK(srcBuf.init(cx, sourceText, js_strlen(sourceText),
321 JS::SourceOwnership::Borrowed));
323 JS::RootedValue val(cx);
324 bool ok = JS::Evaluate(cx, opts, srcBuf, &val);
326 CHECK(!ok);
327 CHECK(JS_IsExceptionPending(cx));
328 CHECK(val.isUndefined());
330 JS::ExceptionStack exnStack(cx);
331 CHECK(JS::GetPendingExceptionStack(cx, &exnStack));
332 CHECK(exnStack.stack());
333 CHECK(exnStack.stack()->is<js::SavedFrame>());
334 JS::Rooted<js::SavedFrame*> savedFrameStack(
335 cx, &exnStack.stack()->as<js::SavedFrame>());
337 CHECK(exnStack.exception().isInt32());
338 CHECK(exnStack.exception().toInt32() == 5);
340 struct {
341 uint32_t line;
342 uint32_t column;
343 const char* source;
344 const char* functionDisplayName;
345 } expected[] = {{4, 7, "filename.js", "three"},
346 {5, 6, "filename.js", "two"},
347 {6, 4, "filename.js", "one"},
348 {7, 2, "filename.js", nullptr}};
350 size_t i = 0;
351 for (JS::Handle<js::SavedFrame*> frame :
352 js::SavedFrame::RootedRange(cx, savedFrameStack)) {
353 CHECK(i < 4);
355 // Line
356 uint32_t line = 123;
357 JS::SavedFrameResult result = JS::GetSavedFrameLine(
358 cx, principals, frame, &line, JS::SavedFrameSelfHosted::Exclude);
359 CHECK(result == JS::SavedFrameResult::Ok);
360 CHECK_EQUAL(line, expected[i].line);
362 // Column
363 uint32_t column = 123;
364 result = JS::GetSavedFrameColumn(cx, principals, frame, &column,
365 JS::SavedFrameSelfHosted::Exclude);
366 CHECK(result == JS::SavedFrameResult::Ok);
367 CHECK_EQUAL(column, expected[i].column);
369 // Source
370 JS::RootedString str(cx);
371 result = JS::GetSavedFrameSource(cx, principals, frame, &str,
372 JS::SavedFrameSelfHosted::Exclude);
373 CHECK(result == JS::SavedFrameResult::Ok);
374 JSLinearString* linear = str->ensureLinear(cx);
375 CHECK(linear);
376 CHECK(js::StringEqualsAscii(linear, expected[i].source));
378 // Function display name
379 result = JS::GetSavedFrameFunctionDisplayName(
380 cx, principals, frame, &str, JS::SavedFrameSelfHosted::Exclude);
381 CHECK(result == JS::SavedFrameResult::Ok);
382 if (auto expectedName = expected[i].functionDisplayName) {
383 CHECK(str);
384 linear = str->ensureLinear(cx);
385 CHECK(linear);
386 CHECK(js::StringEqualsAscii(linear, expectedName));
387 } else {
388 CHECK(!str);
391 i++;
394 return true;
396 END_TEST(test_GetPendingExceptionStack)