Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / js / src / jsapi-tests / testSavedStacks.cpp
blobb53fbd79adb584246302f524bd95d9e342394f69
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/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
11 #include "js/CompilationAndEvaluation.h" // JS::Evaluate
12 #include "js/Exception.h"
13 #include "js/SavedFrameAPI.h"
14 #include "js/SourceText.h" // JS::Source{Ownership,Text}
15 #include "js/Stack.h"
16 #include "jsapi-tests/tests.h"
17 #include "util/Text.h"
18 #include "vm/ArrayObject.h"
19 #include "vm/Realm.h"
20 #include "vm/SavedStacks.h"
22 BEGIN_TEST(testSavedStacks_withNoStack) {
23 JS::Realm* realm = cx->realm();
24 realm->setAllocationMetadataBuilder(&js::SavedStacks::metadataBuilder);
25 JS::RootedObject obj(cx, js::NewDenseEmptyArray(cx));
26 realm->setAllocationMetadataBuilder(nullptr);
27 return true;
29 END_TEST(testSavedStacks_withNoStack)
31 BEGIN_TEST(testSavedStacks_ApiDefaultValues) {
32 JS::Rooted<js::SavedFrame*> savedFrame(cx, nullptr);
34 JSPrincipals* principals = cx->realm()->principals();
36 // Source
37 JS::RootedString str(cx);
38 JS::SavedFrameResult result =
39 JS::GetSavedFrameSource(cx, principals, savedFrame, &str);
40 CHECK(result == JS::SavedFrameResult::AccessDenied);
41 CHECK(str.get() == cx->runtime()->emptyString);
43 // Line
44 uint32_t line = 123;
45 result = JS::GetSavedFrameLine(cx, principals, savedFrame, &line);
46 CHECK(result == JS::SavedFrameResult::AccessDenied);
47 CHECK(line == 0);
49 // Column
50 JS::TaggedColumnNumberOneOrigin column(JS::LimitedColumnNumberOneOrigin(123));
51 result = JS::GetSavedFrameColumn(cx, principals, savedFrame, &column);
52 CHECK(result == JS::SavedFrameResult::AccessDenied);
53 CHECK(column == JS::TaggedColumnNumberOneOrigin());
55 // Function display name
56 result =
57 JS::GetSavedFrameFunctionDisplayName(cx, principals, savedFrame, &str);
58 CHECK(result == JS::SavedFrameResult::AccessDenied);
59 CHECK(str.get() == nullptr);
61 // Parent
62 JS::RootedObject parent(cx);
63 result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent);
64 CHECK(result == JS::SavedFrameResult::AccessDenied);
65 CHECK(parent.get() == nullptr);
67 // Stack string
68 CHECK(JS::BuildStackString(cx, principals, savedFrame, &str));
69 CHECK(str.get() == cx->runtime()->emptyString);
71 return true;
73 END_TEST(testSavedStacks_ApiDefaultValues)
75 BEGIN_TEST(testSavedStacks_RangeBasedForLoops) {
76 CHECK(js::DefineTestingFunctions(cx, global, false, false));
78 JS::RootedValue val(cx);
79 CHECK(
80 evaluate("(function one() { \n" // 1
81 " return (function two() { \n" // 2
82 " return (function three() { \n" // 3
83 " return saveStack(); \n" // 4
84 " }()); \n" // 5
85 " }()); \n" // 6
86 "}()); \n", // 7
87 "filename.js", 1, &val));
89 CHECK(val.isObject());
90 JS::RootedObject obj(cx, &val.toObject());
92 CHECK(obj->is<js::SavedFrame>());
93 JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
95 JS::Rooted<js::SavedFrame*> rf(cx, savedFrame);
96 for (JS::Handle<js::SavedFrame*> frame :
97 js::SavedFrame::RootedRange(cx, rf)) {
98 JS_GC(cx);
99 CHECK(frame == rf);
100 rf = rf->getParent();
102 CHECK(rf == nullptr);
104 // Stack string
105 static const char SpiderMonkeyStack[] =
106 "three@filename.js:4:14\n"
107 "two@filename.js:5:6\n"
108 "one@filename.js:6:4\n"
109 "@filename.js:7:2\n";
110 static const char V8Stack[] =
111 " at three (filename.js:4:14)\n"
112 " at two (filename.js:5:6)\n"
113 " at one (filename.js:6:4)\n"
114 " at filename.js:7:2";
115 struct {
116 js::StackFormat format;
117 const char* expected;
118 } expectations[] = {{js::StackFormat::Default, SpiderMonkeyStack},
119 {js::StackFormat::SpiderMonkey, SpiderMonkeyStack},
120 {js::StackFormat::V8, V8Stack}};
121 auto CheckStacks = [&]() {
122 for (auto& expectation : expectations) {
123 JS::RootedString str(cx);
124 JSPrincipals* principals = cx->realm()->principals();
125 CHECK(JS::BuildStackString(cx, principals, savedFrame, &str, 0,
126 expectation.format));
127 JSLinearString* lin = str->ensureLinear(cx);
128 CHECK(lin);
129 CHECK(js::StringEqualsAscii(lin, expectation.expected));
131 return true;
134 CHECK(CheckStacks());
136 js::SetStackFormat(cx, js::StackFormat::V8);
137 expectations[0].expected = V8Stack;
139 CHECK(CheckStacks());
141 return true;
143 END_TEST(testSavedStacks_RangeBasedForLoops)
145 BEGIN_TEST(testSavedStacks_ErrorStackSpiderMonkey) {
146 JS::RootedValue val(cx);
147 CHECK(
148 evaluate("(function one() { \n" // 1
149 " return (function two() { \n" // 2
150 " return (function three() { \n" // 3
151 " return new Error('foo'); \n" // 4
152 " }()); \n" // 5
153 " }()); \n" // 6
154 "}()).stack \n", // 7
155 "filename.js", 1, &val));
157 CHECK(val.isString());
158 JS::RootedString stack(cx, val.toString());
160 // Stack string
161 static const char SpiderMonkeyStack[] =
162 "three@filename.js:4:14\n"
163 "two@filename.js:5:6\n"
164 "one@filename.js:6:4\n"
165 "@filename.js:7:2\n";
166 JSLinearString* lin = stack->ensureLinear(cx);
167 CHECK(lin);
168 CHECK(js::StringEqualsLiteral(lin, SpiderMonkeyStack));
170 return true;
172 END_TEST(testSavedStacks_ErrorStackSpiderMonkey)
174 BEGIN_TEST(testSavedStacks_ErrorStackV8) {
175 js::SetStackFormat(cx, js::StackFormat::V8);
177 JS::RootedValue val(cx);
178 CHECK(
179 evaluate("(function one() { \n" // 1
180 " return (function two() { \n" // 2
181 " return (function three() { \n" // 3
182 " return new Error('foo'); \n" // 4
183 " }()); \n" // 5
184 " }()); \n" // 6
185 "}()).stack \n", // 7
186 "filename.js", 1, &val));
188 CHECK(val.isString());
189 JS::RootedString stack(cx, val.toString());
191 // Stack string
192 static const char V8Stack[] =
193 "Error: foo\n"
194 " at three (filename.js:4:14)\n"
195 " at two (filename.js:5:6)\n"
196 " at one (filename.js:6:4)\n"
197 " at filename.js:7:2";
198 JSLinearString* lin = stack->ensureLinear(cx);
199 CHECK(lin);
200 CHECK(js::StringEqualsLiteral(lin, V8Stack));
202 return true;
204 END_TEST(testSavedStacks_ErrorStackV8)
206 BEGIN_TEST(testSavedStacks_selfHostedFrames) {
207 CHECK(js::DefineTestingFunctions(cx, global, false, false));
209 JS::RootedValue val(cx);
210 // 0 1 2 3
211 // 0123456789012345678901234567890123456789
212 CHECK(
213 evaluate("(function one() { \n" // 1
214 " try { \n" // 2
215 " [1].map(function two() { \n" // 3
216 " throw saveStack(); \n" // 4
217 " }); \n" // 5
218 " } catch (stack) { \n" // 6
219 " return stack; \n" // 7
220 " } \n" // 8
221 "}()) \n", // 9
222 "filename.js", 1, &val));
224 CHECK(val.isObject());
225 JS::RootedObject obj(cx, &val.toObject());
227 CHECK(obj->is<js::SavedFrame>());
228 JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
230 JS::Rooted<js::SavedFrame*> selfHostedFrame(cx, savedFrame->getParent());
231 CHECK(selfHostedFrame->isSelfHosted(cx));
233 JSPrincipals* principals = cx->realm()->principals();
235 // Source
236 JS::RootedString str(cx);
237 JS::SavedFrameResult result = JS::GetSavedFrameSource(
238 cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude);
239 CHECK(result == JS::SavedFrameResult::Ok);
240 JSLinearString* lin = str->ensureLinear(cx);
241 CHECK(lin);
242 CHECK(js::StringEqualsLiteral(lin, "filename.js"));
244 // Source, including self-hosted frames
245 result = JS::GetSavedFrameSource(cx, principals, selfHostedFrame, &str,
246 JS::SavedFrameSelfHosted::Include);
247 CHECK(result == JS::SavedFrameResult::Ok);
248 lin = str->ensureLinear(cx);
249 CHECK(lin);
250 CHECK(js::StringEqualsLiteral(lin, "self-hosted"));
252 // Line
253 uint32_t line = 123;
254 result = JS::GetSavedFrameLine(cx, principals, selfHostedFrame, &line,
255 JS::SavedFrameSelfHosted::Exclude);
256 CHECK(result == JS::SavedFrameResult::Ok);
257 CHECK_EQUAL(line, 3U);
259 // Column
260 JS::TaggedColumnNumberOneOrigin column(JS::LimitedColumnNumberOneOrigin(123));
261 result = JS::GetSavedFrameColumn(cx, principals, selfHostedFrame, &column,
262 JS::SavedFrameSelfHosted::Exclude);
263 CHECK(result == JS::SavedFrameResult::Ok);
264 CHECK_EQUAL(column.oneOriginValue(), 9U);
266 // Function display name
267 result = JS::GetSavedFrameFunctionDisplayName(
268 cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude);
269 CHECK(result == JS::SavedFrameResult::Ok);
270 lin = str->ensureLinear(cx);
271 CHECK(lin);
272 CHECK(js::StringEqualsLiteral(lin, "one"));
274 // Parent
275 JS::RootedObject parent(cx);
276 result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent,
277 JS::SavedFrameSelfHosted::Exclude);
278 CHECK(result == JS::SavedFrameResult::Ok);
279 // JS::GetSavedFrameParent does this super funky and potentially unexpected
280 // thing where it doesn't return the next subsumed parent but any next
281 // parent. This so that callers can still get the "asyncParent" property
282 // which is only on the first frame of the async parent stack and that frame
283 // might not be subsumed by the caller. It is expected that callers will
284 // still interact with the frame through the JSAPI accessors, so this should
285 // be safe and should not leak privileged info to unprivileged
286 // callers. However, because of that, we don't test that the parent we get
287 // here is the selfHostedFrame's parent (because, as just explained, it
288 // isn't) and instead check that asking for the source property gives us the
289 // expected value.
290 result = JS::GetSavedFrameSource(cx, principals, parent, &str,
291 JS::SavedFrameSelfHosted::Exclude);
292 CHECK(result == JS::SavedFrameResult::Ok);
293 lin = str->ensureLinear(cx);
294 CHECK(lin);
295 CHECK(js::StringEqualsLiteral(lin, "filename.js"));
297 return true;
299 END_TEST(testSavedStacks_selfHostedFrames)
301 BEGIN_TEST(test_GetPendingExceptionStack) {
302 CHECK(js::DefineTestingFunctions(cx, global, false, false));
304 JSPrincipals* principals = cx->realm()->principals();
306 static const char sourceText[] =
307 // 1 2 3
308 // 123456789012345678901234567890123456789
309 "(function one() { \n" // 1
310 " (function two() { \n" // 2
311 " (function three() { \n" // 3
312 " throw 5; \n" // 4
313 " }()); \n" // 5
314 " }()); \n" // 6
315 "}()) \n"; // 7
317 JS::CompileOptions opts(cx);
318 opts.setFileAndLine("filename.js", 1U);
320 JS::SourceText<mozilla::Utf8Unit> srcBuf;
321 CHECK(srcBuf.init(cx, sourceText, js_strlen(sourceText),
322 JS::SourceOwnership::Borrowed));
324 JS::RootedValue val(cx);
325 bool ok = JS::Evaluate(cx, opts, srcBuf, &val);
327 CHECK(!ok);
328 CHECK(JS_IsExceptionPending(cx));
329 CHECK(val.isUndefined());
331 JS::ExceptionStack exnStack(cx);
332 CHECK(JS::GetPendingExceptionStack(cx, &exnStack));
333 CHECK(exnStack.stack());
334 CHECK(exnStack.stack()->is<js::SavedFrame>());
335 JS::Rooted<js::SavedFrame*> savedFrameStack(
336 cx, &exnStack.stack()->as<js::SavedFrame>());
338 CHECK(exnStack.exception().isInt32());
339 CHECK(exnStack.exception().toInt32() == 5);
341 struct {
342 uint32_t line;
343 uint32_t column;
344 const char* source;
345 const char* functionDisplayName;
346 } expected[] = {{4, 7, "filename.js", "three"},
347 {5, 6, "filename.js", "two"},
348 {6, 4, "filename.js", "one"},
349 {7, 2, "filename.js", nullptr}};
351 size_t i = 0;
352 for (JS::Handle<js::SavedFrame*> frame :
353 js::SavedFrame::RootedRange(cx, savedFrameStack)) {
354 CHECK(i < 4);
356 // Line
357 uint32_t line = 123;
358 JS::SavedFrameResult result = JS::GetSavedFrameLine(
359 cx, principals, frame, &line, JS::SavedFrameSelfHosted::Exclude);
360 CHECK(result == JS::SavedFrameResult::Ok);
361 CHECK_EQUAL(line, expected[i].line);
363 // Column
364 JS::TaggedColumnNumberOneOrigin column(
365 JS::LimitedColumnNumberOneOrigin(123));
366 result = JS::GetSavedFrameColumn(cx, principals, frame, &column,
367 JS::SavedFrameSelfHosted::Exclude);
368 CHECK(result == JS::SavedFrameResult::Ok);
369 CHECK_EQUAL(column.oneOriginValue(), expected[i].column);
371 // Source
372 JS::RootedString str(cx);
373 result = JS::GetSavedFrameSource(cx, principals, frame, &str,
374 JS::SavedFrameSelfHosted::Exclude);
375 CHECK(result == JS::SavedFrameResult::Ok);
376 JSLinearString* linear = str->ensureLinear(cx);
377 CHECK(linear);
378 CHECK(js::StringEqualsAscii(linear, expected[i].source));
380 // Function display name
381 result = JS::GetSavedFrameFunctionDisplayName(
382 cx, principals, frame, &str, JS::SavedFrameSelfHosted::Exclude);
383 CHECK(result == JS::SavedFrameResult::Ok);
384 if (auto expectedName = expected[i].functionDisplayName) {
385 CHECK(str);
386 linear = str->ensureLinear(cx);
387 CHECK(linear);
388 CHECK(js::StringEqualsAscii(linear, expectedName));
389 } else {
390 CHECK(!str);
393 i++;
396 return true;
398 END_TEST(test_GetPendingExceptionStack)