summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests/testSavedStacks.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jsapi-tests/testSavedStacks.cpp')
-rw-r--r--js/src/jsapi-tests/testSavedStacks.cpp398
1 files changed, 398 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testSavedStacks.cpp b/js/src/jsapi-tests/testSavedStacks.cpp
new file mode 100644
index 0000000000..b53fbd79ad
--- /dev/null
+++ b/js/src/jsapi-tests/testSavedStacks.cpp
@@ -0,0 +1,398 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include "builtin/TestingFunctions.h"
+#include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
+#include "js/CompilationAndEvaluation.h" // JS::Evaluate
+#include "js/Exception.h"
+#include "js/SavedFrameAPI.h"
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+#include "js/Stack.h"
+#include "jsapi-tests/tests.h"
+#include "util/Text.h"
+#include "vm/ArrayObject.h"
+#include "vm/Realm.h"
+#include "vm/SavedStacks.h"
+
+BEGIN_TEST(testSavedStacks_withNoStack) {
+ JS::Realm* realm = cx->realm();
+ realm->setAllocationMetadataBuilder(&js::SavedStacks::metadataBuilder);
+ JS::RootedObject obj(cx, js::NewDenseEmptyArray(cx));
+ realm->setAllocationMetadataBuilder(nullptr);
+ return true;
+}
+END_TEST(testSavedStacks_withNoStack)
+
+BEGIN_TEST(testSavedStacks_ApiDefaultValues) {
+ JS::Rooted<js::SavedFrame*> savedFrame(cx, nullptr);
+
+ JSPrincipals* principals = cx->realm()->principals();
+
+ // Source
+ JS::RootedString str(cx);
+ JS::SavedFrameResult result =
+ JS::GetSavedFrameSource(cx, principals, savedFrame, &str);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(str.get() == cx->runtime()->emptyString);
+
+ // Line
+ uint32_t line = 123;
+ result = JS::GetSavedFrameLine(cx, principals, savedFrame, &line);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(line == 0);
+
+ // Column
+ JS::TaggedColumnNumberOneOrigin column(JS::LimitedColumnNumberOneOrigin(123));
+ result = JS::GetSavedFrameColumn(cx, principals, savedFrame, &column);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(column == JS::TaggedColumnNumberOneOrigin());
+
+ // Function display name
+ result =
+ JS::GetSavedFrameFunctionDisplayName(cx, principals, savedFrame, &str);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(str.get() == nullptr);
+
+ // Parent
+ JS::RootedObject parent(cx);
+ result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent);
+ CHECK(result == JS::SavedFrameResult::AccessDenied);
+ CHECK(parent.get() == nullptr);
+
+ // Stack string
+ CHECK(JS::BuildStackString(cx, principals, savedFrame, &str));
+ CHECK(str.get() == cx->runtime()->emptyString);
+
+ return true;
+}
+END_TEST(testSavedStacks_ApiDefaultValues)
+
+BEGIN_TEST(testSavedStacks_RangeBasedForLoops) {
+ CHECK(js::DefineTestingFunctions(cx, global, false, false));
+
+ JS::RootedValue val(cx);
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " return (function two() { \n" // 2
+ " return (function three() { \n" // 3
+ " return saveStack(); \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()); \n", // 7
+ "filename.js", 1, &val));
+
+ CHECK(val.isObject());
+ JS::RootedObject obj(cx, &val.toObject());
+
+ CHECK(obj->is<js::SavedFrame>());
+ JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
+
+ JS::Rooted<js::SavedFrame*> rf(cx, savedFrame);
+ for (JS::Handle<js::SavedFrame*> frame :
+ js::SavedFrame::RootedRange(cx, rf)) {
+ JS_GC(cx);
+ CHECK(frame == rf);
+ rf = rf->getParent();
+ }
+ CHECK(rf == nullptr);
+
+ // Stack string
+ static const char SpiderMonkeyStack[] =
+ "three@filename.js:4:14\n"
+ "two@filename.js:5:6\n"
+ "one@filename.js:6:4\n"
+ "@filename.js:7:2\n";
+ static const char V8Stack[] =
+ " at three (filename.js:4:14)\n"
+ " at two (filename.js:5:6)\n"
+ " at one (filename.js:6:4)\n"
+ " at filename.js:7:2";
+ struct {
+ js::StackFormat format;
+ const char* expected;
+ } expectations[] = {{js::StackFormat::Default, SpiderMonkeyStack},
+ {js::StackFormat::SpiderMonkey, SpiderMonkeyStack},
+ {js::StackFormat::V8, V8Stack}};
+ auto CheckStacks = [&]() {
+ for (auto& expectation : expectations) {
+ JS::RootedString str(cx);
+ JSPrincipals* principals = cx->realm()->principals();
+ CHECK(JS::BuildStackString(cx, principals, savedFrame, &str, 0,
+ expectation.format));
+ JSLinearString* lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsAscii(lin, expectation.expected));
+ }
+ return true;
+ };
+
+ CHECK(CheckStacks());
+
+ js::SetStackFormat(cx, js::StackFormat::V8);
+ expectations[0].expected = V8Stack;
+
+ CHECK(CheckStacks());
+
+ return true;
+}
+END_TEST(testSavedStacks_RangeBasedForLoops)
+
+BEGIN_TEST(testSavedStacks_ErrorStackSpiderMonkey) {
+ JS::RootedValue val(cx);
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " return (function two() { \n" // 2
+ " return (function three() { \n" // 3
+ " return new Error('foo'); \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()).stack \n", // 7
+ "filename.js", 1, &val));
+
+ CHECK(val.isString());
+ JS::RootedString stack(cx, val.toString());
+
+ // Stack string
+ static const char SpiderMonkeyStack[] =
+ "three@filename.js:4:14\n"
+ "two@filename.js:5:6\n"
+ "one@filename.js:6:4\n"
+ "@filename.js:7:2\n";
+ JSLinearString* lin = stack->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, SpiderMonkeyStack));
+
+ return true;
+}
+END_TEST(testSavedStacks_ErrorStackSpiderMonkey)
+
+BEGIN_TEST(testSavedStacks_ErrorStackV8) {
+ js::SetStackFormat(cx, js::StackFormat::V8);
+
+ JS::RootedValue val(cx);
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " return (function two() { \n" // 2
+ " return (function three() { \n" // 3
+ " return new Error('foo'); \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()).stack \n", // 7
+ "filename.js", 1, &val));
+
+ CHECK(val.isString());
+ JS::RootedString stack(cx, val.toString());
+
+ // Stack string
+ static const char V8Stack[] =
+ "Error: foo\n"
+ " at three (filename.js:4:14)\n"
+ " at two (filename.js:5:6)\n"
+ " at one (filename.js:6:4)\n"
+ " at filename.js:7:2";
+ JSLinearString* lin = stack->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, V8Stack));
+
+ return true;
+}
+END_TEST(testSavedStacks_ErrorStackV8)
+
+BEGIN_TEST(testSavedStacks_selfHostedFrames) {
+ CHECK(js::DefineTestingFunctions(cx, global, false, false));
+
+ JS::RootedValue val(cx);
+ // 0 1 2 3
+ // 0123456789012345678901234567890123456789
+ CHECK(
+ evaluate("(function one() { \n" // 1
+ " try { \n" // 2
+ " [1].map(function two() { \n" // 3
+ " throw saveStack(); \n" // 4
+ " }); \n" // 5
+ " } catch (stack) { \n" // 6
+ " return stack; \n" // 7
+ " } \n" // 8
+ "}()) \n", // 9
+ "filename.js", 1, &val));
+
+ CHECK(val.isObject());
+ JS::RootedObject obj(cx, &val.toObject());
+
+ CHECK(obj->is<js::SavedFrame>());
+ JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
+
+ JS::Rooted<js::SavedFrame*> selfHostedFrame(cx, savedFrame->getParent());
+ CHECK(selfHostedFrame->isSelfHosted(cx));
+
+ JSPrincipals* principals = cx->realm()->principals();
+
+ // Source
+ JS::RootedString str(cx);
+ JS::SavedFrameResult result = JS::GetSavedFrameSource(
+ cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ JSLinearString* lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, "filename.js"));
+
+ // Source, including self-hosted frames
+ result = JS::GetSavedFrameSource(cx, principals, selfHostedFrame, &str,
+ JS::SavedFrameSelfHosted::Include);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, "self-hosted"));
+
+ // Line
+ uint32_t line = 123;
+ result = JS::GetSavedFrameLine(cx, principals, selfHostedFrame, &line,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ CHECK_EQUAL(line, 3U);
+
+ // Column
+ JS::TaggedColumnNumberOneOrigin column(JS::LimitedColumnNumberOneOrigin(123));
+ result = JS::GetSavedFrameColumn(cx, principals, selfHostedFrame, &column,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ CHECK_EQUAL(column.oneOriginValue(), 9U);
+
+ // Function display name
+ result = JS::GetSavedFrameFunctionDisplayName(
+ cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, "one"));
+
+ // Parent
+ JS::RootedObject parent(cx);
+ result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ // JS::GetSavedFrameParent does this super funky and potentially unexpected
+ // thing where it doesn't return the next subsumed parent but any next
+ // parent. This so that callers can still get the "asyncParent" property
+ // which is only on the first frame of the async parent stack and that frame
+ // might not be subsumed by the caller. It is expected that callers will
+ // still interact with the frame through the JSAPI accessors, so this should
+ // be safe and should not leak privileged info to unprivileged
+ // callers. However, because of that, we don't test that the parent we get
+ // here is the selfHostedFrame's parent (because, as just explained, it
+ // isn't) and instead check that asking for the source property gives us the
+ // expected value.
+ result = JS::GetSavedFrameSource(cx, principals, parent, &str,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ lin = str->ensureLinear(cx);
+ CHECK(lin);
+ CHECK(js::StringEqualsLiteral(lin, "filename.js"));
+
+ return true;
+}
+END_TEST(testSavedStacks_selfHostedFrames)
+
+BEGIN_TEST(test_GetPendingExceptionStack) {
+ CHECK(js::DefineTestingFunctions(cx, global, false, false));
+
+ JSPrincipals* principals = cx->realm()->principals();
+
+ static const char sourceText[] =
+ // 1 2 3
+ // 123456789012345678901234567890123456789
+ "(function one() { \n" // 1
+ " (function two() { \n" // 2
+ " (function three() { \n" // 3
+ " throw 5; \n" // 4
+ " }()); \n" // 5
+ " }()); \n" // 6
+ "}()) \n"; // 7
+
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine("filename.js", 1U);
+
+ JS::SourceText<mozilla::Utf8Unit> srcBuf;
+ CHECK(srcBuf.init(cx, sourceText, js_strlen(sourceText),
+ JS::SourceOwnership::Borrowed));
+
+ JS::RootedValue val(cx);
+ bool ok = JS::Evaluate(cx, opts, srcBuf, &val);
+
+ CHECK(!ok);
+ CHECK(JS_IsExceptionPending(cx));
+ CHECK(val.isUndefined());
+
+ JS::ExceptionStack exnStack(cx);
+ CHECK(JS::GetPendingExceptionStack(cx, &exnStack));
+ CHECK(exnStack.stack());
+ CHECK(exnStack.stack()->is<js::SavedFrame>());
+ JS::Rooted<js::SavedFrame*> savedFrameStack(
+ cx, &exnStack.stack()->as<js::SavedFrame>());
+
+ CHECK(exnStack.exception().isInt32());
+ CHECK(exnStack.exception().toInt32() == 5);
+
+ struct {
+ uint32_t line;
+ uint32_t column;
+ const char* source;
+ const char* functionDisplayName;
+ } expected[] = {{4, 7, "filename.js", "three"},
+ {5, 6, "filename.js", "two"},
+ {6, 4, "filename.js", "one"},
+ {7, 2, "filename.js", nullptr}};
+
+ size_t i = 0;
+ for (JS::Handle<js::SavedFrame*> frame :
+ js::SavedFrame::RootedRange(cx, savedFrameStack)) {
+ CHECK(i < 4);
+
+ // Line
+ uint32_t line = 123;
+ JS::SavedFrameResult result = JS::GetSavedFrameLine(
+ cx, principals, frame, &line, JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ CHECK_EQUAL(line, expected[i].line);
+
+ // Column
+ JS::TaggedColumnNumberOneOrigin column(
+ JS::LimitedColumnNumberOneOrigin(123));
+ result = JS::GetSavedFrameColumn(cx, principals, frame, &column,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ CHECK_EQUAL(column.oneOriginValue(), expected[i].column);
+
+ // Source
+ JS::RootedString str(cx);
+ result = JS::GetSavedFrameSource(cx, principals, frame, &str,
+ JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ JSLinearString* linear = str->ensureLinear(cx);
+ CHECK(linear);
+ CHECK(js::StringEqualsAscii(linear, expected[i].source));
+
+ // Function display name
+ result = JS::GetSavedFrameFunctionDisplayName(
+ cx, principals, frame, &str, JS::SavedFrameSelfHosted::Exclude);
+ CHECK(result == JS::SavedFrameResult::Ok);
+ if (auto expectedName = expected[i].functionDisplayName) {
+ CHECK(str);
+ linear = str->ensureLinear(cx);
+ CHECK(linear);
+ CHECK(js::StringEqualsAscii(linear, expectedName));
+ } else {
+ CHECK(!str);
+ }
+
+ i++;
+ }
+
+ return true;
+}
+END_TEST(test_GetPendingExceptionStack)