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}
16 #include "jsapi-tests/tests.h"
17 #include "util/Text.h"
18 #include "vm/ArrayObject.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);
29 END_TEST(testSavedStacks_withNoStack
)
31 BEGIN_TEST(testSavedStacks_ApiDefaultValues
) {
32 JS::Rooted
<js::SavedFrame
*> savedFrame(cx
, nullptr);
34 JSPrincipals
* principals
= cx
->realm()->principals();
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
);
45 result
= JS::GetSavedFrameLine(cx
, principals
, savedFrame
, &line
);
46 CHECK(result
== JS::SavedFrameResult::AccessDenied
);
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
57 JS::GetSavedFrameFunctionDisplayName(cx
, principals
, savedFrame
, &str
);
58 CHECK(result
== JS::SavedFrameResult::AccessDenied
);
59 CHECK(str
.get() == nullptr);
62 JS::RootedObject
parent(cx
);
63 result
= JS::GetSavedFrameParent(cx
, principals
, savedFrame
, &parent
);
64 CHECK(result
== JS::SavedFrameResult::AccessDenied
);
65 CHECK(parent
.get() == nullptr);
68 CHECK(JS::BuildStackString(cx
, principals
, savedFrame
, &str
));
69 CHECK(str
.get() == cx
->runtime()->emptyString
);
73 END_TEST(testSavedStacks_ApiDefaultValues
)
75 BEGIN_TEST(testSavedStacks_RangeBasedForLoops
) {
76 CHECK(js::DefineTestingFunctions(cx
, global
, false, false));
78 JS::RootedValue
val(cx
);
80 evaluate("(function one() { \n" // 1
81 " return (function two() { \n" // 2
82 " return (function three() { \n" // 3
83 " return saveStack(); \n" // 4
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
)) {
100 rf
= rf
->getParent();
102 CHECK(rf
== nullptr);
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";
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
);
129 CHECK(js::StringEqualsAscii(lin
, expectation
.expected
));
134 CHECK(CheckStacks());
136 js::SetStackFormat(cx
, js::StackFormat::V8
);
137 expectations
[0].expected
= V8Stack
;
139 CHECK(CheckStacks());
143 END_TEST(testSavedStacks_RangeBasedForLoops
)
145 BEGIN_TEST(testSavedStacks_ErrorStackSpiderMonkey
) {
146 JS::RootedValue
val(cx
);
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
154 "}()).stack \n", // 7
155 "filename.js", 1, &val
));
157 CHECK(val
.isString());
158 JS::RootedString
stack(cx
, val
.toString());
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
);
168 CHECK(js::StringEqualsLiteral(lin
, SpiderMonkeyStack
));
172 END_TEST(testSavedStacks_ErrorStackSpiderMonkey
)
174 BEGIN_TEST(testSavedStacks_ErrorStackV8
) {
175 js::SetStackFormat(cx
, js::StackFormat::V8
);
177 JS::RootedValue
val(cx
);
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
185 "}()).stack \n", // 7
186 "filename.js", 1, &val
));
188 CHECK(val
.isString());
189 JS::RootedString
stack(cx
, val
.toString());
192 static const char V8Stack
[] =
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
);
200 CHECK(js::StringEqualsLiteral(lin
, V8Stack
));
204 END_TEST(testSavedStacks_ErrorStackV8
)
206 BEGIN_TEST(testSavedStacks_selfHostedFrames
) {
207 CHECK(js::DefineTestingFunctions(cx
, global
, false, false));
209 JS::RootedValue
val(cx
);
211 // 0123456789012345678901234567890123456789
213 evaluate("(function one() { \n" // 1
215 " [1].map(function two() { \n" // 3
216 " throw saveStack(); \n" // 4
218 " } catch (stack) { \n" // 6
219 " return stack; \n" // 7
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();
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
);
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
);
250 CHECK(js::StringEqualsLiteral(lin
, "self-hosted"));
254 result
= JS::GetSavedFrameLine(cx
, principals
, selfHostedFrame
, &line
,
255 JS::SavedFrameSelfHosted::Exclude
);
256 CHECK(result
== JS::SavedFrameResult::Ok
);
257 CHECK_EQUAL(line
, 3U);
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
);
272 CHECK(js::StringEqualsLiteral(lin
, "one"));
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
290 result
= JS::GetSavedFrameSource(cx
, principals
, parent
, &str
,
291 JS::SavedFrameSelfHosted::Exclude
);
292 CHECK(result
== JS::SavedFrameResult::Ok
);
293 lin
= str
->ensureLinear(cx
);
295 CHECK(js::StringEqualsLiteral(lin
, "filename.js"));
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
[] =
308 // 123456789012345678901234567890123456789
309 "(function one() { \n" // 1
310 " (function two() { \n" // 2
311 " (function three() { \n" // 3
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
);
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);
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}};
352 for (JS::Handle
<js::SavedFrame
*> frame
:
353 js::SavedFrame::RootedRange(cx
, savedFrameStack
)) {
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
);
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
);
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
);
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
) {
386 linear
= str
->ensureLinear(cx
);
388 CHECK(js::StringEqualsAscii(linear
, expectedName
));
398 END_TEST(test_GetPendingExceptionStack
)