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}
15 #include "jsapi-tests/tests.h"
16 #include "util/Text.h"
17 #include "vm/ArrayObject.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);
28 END_TEST(testSavedStacks_withNoStack
)
30 BEGIN_TEST(testSavedStacks_ApiDefaultValues
) {
31 JS::Rooted
<js::SavedFrame
*> savedFrame(cx
, nullptr);
33 JSPrincipals
* principals
= cx
->realm()->principals();
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
);
44 result
= JS::GetSavedFrameLine(cx
, principals
, savedFrame
, &line
);
45 CHECK(result
== JS::SavedFrameResult::AccessDenied
);
49 uint32_t column
= 123;
50 result
= JS::GetSavedFrameColumn(cx
, principals
, savedFrame
, &column
);
51 CHECK(result
== JS::SavedFrameResult::AccessDenied
);
54 // Function display name
56 JS::GetSavedFrameFunctionDisplayName(cx
, principals
, savedFrame
, &str
);
57 CHECK(result
== JS::SavedFrameResult::AccessDenied
);
58 CHECK(str
.get() == nullptr);
61 JS::RootedObject
parent(cx
);
62 result
= JS::GetSavedFrameParent(cx
, principals
, savedFrame
, &parent
);
63 CHECK(result
== JS::SavedFrameResult::AccessDenied
);
64 CHECK(parent
.get() == nullptr);
67 CHECK(JS::BuildStackString(cx
, principals
, savedFrame
, &str
));
68 CHECK(str
.get() == cx
->runtime()->emptyString
);
72 END_TEST(testSavedStacks_ApiDefaultValues
)
74 BEGIN_TEST(testSavedStacks_RangeBasedForLoops
) {
75 CHECK(js::DefineTestingFunctions(cx
, global
, false, false));
77 JS::RootedValue
val(cx
);
79 evaluate("(function one() { \n" // 1
80 " return (function two() { \n" // 2
81 " return (function three() { \n" // 3
82 " return saveStack(); \n" // 4
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
)) {
101 CHECK(rf
== nullptr);
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";
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
);
128 CHECK(js::StringEqualsAscii(lin
, expectation
.expected
));
133 CHECK(CheckStacks());
135 js::SetStackFormat(cx
, js::StackFormat::V8
);
136 expectations
[0].expected
= V8Stack
;
138 CHECK(CheckStacks());
142 END_TEST(testSavedStacks_RangeBasedForLoops
)
144 BEGIN_TEST(testSavedStacks_ErrorStackSpiderMonkey
) {
145 JS::RootedValue
val(cx
);
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
153 "}()).stack \n", // 7
154 "filename.js", 1, &val
));
156 CHECK(val
.isString());
157 JS::RootedString
stack(cx
, val
.toString());
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
);
167 CHECK(js::StringEqualsLiteral(lin
, SpiderMonkeyStack
));
171 END_TEST(testSavedStacks_ErrorStackSpiderMonkey
)
173 BEGIN_TEST(testSavedStacks_ErrorStackV8
) {
174 js::SetStackFormat(cx
, js::StackFormat::V8
);
176 JS::RootedValue
val(cx
);
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
184 "}()).stack \n", // 7
185 "filename.js", 1, &val
));
187 CHECK(val
.isString());
188 JS::RootedString
stack(cx
, val
.toString());
191 static const char V8Stack
[] =
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
);
199 CHECK(js::StringEqualsLiteral(lin
, V8Stack
));
203 END_TEST(testSavedStacks_ErrorStackV8
)
205 BEGIN_TEST(testSavedStacks_selfHostedFrames
) {
206 CHECK(js::DefineTestingFunctions(cx
, global
, false, false));
208 JS::RootedValue
val(cx
);
210 // 0123456789012345678901234567890123456789
212 evaluate("(function one() { \n" // 1
214 " [1].map(function two() { \n" // 3
215 " throw saveStack(); \n" // 4
217 " } catch (stack) { \n" // 6
218 " return stack; \n" // 7
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();
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
);
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
);
249 CHECK(js::StringEqualsLiteral(lin
, "self-hosted"));
253 result
= JS::GetSavedFrameLine(cx
, principals
, selfHostedFrame
, &line
,
254 JS::SavedFrameSelfHosted::Exclude
);
255 CHECK(result
== JS::SavedFrameResult::Ok
);
256 CHECK_EQUAL(line
, 3U);
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
);
271 CHECK(js::StringEqualsLiteral(lin
, "one"));
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
289 result
= JS::GetSavedFrameSource(cx
, principals
, parent
, &str
,
290 JS::SavedFrameSelfHosted::Exclude
);
291 CHECK(result
== JS::SavedFrameResult::Ok
);
292 lin
= str
->ensureLinear(cx
);
294 CHECK(js::StringEqualsLiteral(lin
, "filename.js"));
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
[] =
307 // 123456789012345678901234567890123456789
308 "(function one() { \n" // 1
309 " (function two() { \n" // 2
310 " (function three() { \n" // 3
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
);
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);
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}};
351 for (JS::Handle
<js::SavedFrame
*> frame
:
352 js::SavedFrame::RootedRange(cx
, savedFrameStack
)) {
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
);
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
);
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
);
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
) {
384 linear
= str
->ensureLinear(cx
);
386 CHECK(js::StringEqualsAscii(linear
, expectedName
));
396 END_TEST(test_GetPendingExceptionStack
)