| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * 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 "jscompartment.h" |
| #include "jsfriendapi.h" |
| #include "jsstr.h" |
| |
| #include "builtin/TestingFunctions.h" |
| #include "jsapi-tests/tests.h" |
| #include "vm/ArrayObject.h" |
| #include "vm/SavedStacks.h" |
| |
| BEGIN_TEST(testSavedStacks_withNoStack) |
| { |
| JSCompartment* compartment = js::GetContextCompartment(cx); |
| compartment->setObjectMetadataCallback(js::SavedStacksMetadataCallback); |
| JS::RootedObject obj(cx, js::NewDenseEmptyArray(cx)); |
| compartment->setObjectMetadataCallback(nullptr); |
| return true; |
| } |
| END_TEST(testSavedStacks_withNoStack) |
| |
| BEGIN_TEST(testSavedStacks_ApiDefaultValues) |
| { |
| js::RootedSavedFrame savedFrame(cx, nullptr); |
| |
| // Source |
| JS::RootedString str(cx); |
| JS::SavedFrameResult result = JS::GetSavedFrameSource(cx, savedFrame, &str); |
| CHECK(result == JS::SavedFrameResult::AccessDenied); |
| CHECK(str.get() == cx->runtime()->emptyString); |
| |
| // Line |
| uint32_t line = 123; |
| result = JS::GetSavedFrameLine(cx, savedFrame, &line); |
| CHECK(result == JS::SavedFrameResult::AccessDenied); |
| CHECK(line == 0); |
| |
| // Column |
| uint32_t column = 123; |
| result = JS::GetSavedFrameColumn(cx, savedFrame, &column); |
| CHECK(result == JS::SavedFrameResult::AccessDenied); |
| CHECK(column == 0); |
| |
| // Function display name |
| result = JS::GetSavedFrameFunctionDisplayName(cx, savedFrame, &str); |
| CHECK(result == JS::SavedFrameResult::AccessDenied); |
| CHECK(str.get() == nullptr); |
| |
| // Parent |
| JS::RootedObject parent(cx); |
| result = JS::GetSavedFrameParent(cx, savedFrame, &parent); |
| CHECK(result == JS::SavedFrameResult::AccessDenied); |
| CHECK(parent.get() == nullptr); |
| |
| // Stack string |
| CHECK(JS::BuildStackString(cx, 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::SavedFrame* f = savedFrame.get(); |
| for (auto& frame : *savedFrame.get()) { |
| CHECK(&frame == f); |
| f = f->getParent(); |
| } |
| CHECK(f == nullptr); |
| |
| const js::SavedFrame* cf = savedFrame.get(); |
| for (const auto& frame : *savedFrame.get()) { |
| CHECK(&frame == cf); |
| cf = cf->getParent(); |
| } |
| CHECK(cf == nullptr); |
| |
| JS::Rooted<js::SavedFrame*> rf(cx, savedFrame); |
| for (JS::Handle<js::SavedFrame*> frame : js::SavedFrame::RootedRange(cx, rf)) { |
| JS_GC(cx->runtime()); |
| CHECK(frame == rf); |
| rf = rf->getParent(); |
| } |
| CHECK(rf == nullptr); |
| |
| return true; |
| } |
| END_TEST(testSavedStacks_RangeBasedForLoops) |
| |
| 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()); |
| |
| // Source |
| JS::RootedString str(cx); |
| JS::SavedFrameResult result = JS::GetSavedFrameSource(cx, selfHostedFrame, &str, |
| JS::SavedFrameSelfHosted::Exclude); |
| CHECK(result == JS::SavedFrameResult::Ok); |
| JSLinearString* lin = str->ensureLinear(cx); |
| CHECK(lin); |
| CHECK(js::StringEqualsAscii(lin, "filename.js")); |
| |
| // Source, including self-hosted frames |
| result = JS::GetSavedFrameSource(cx, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Include); |
| CHECK(result == JS::SavedFrameResult::Ok); |
| lin = str->ensureLinear(cx); |
| CHECK(lin); |
| CHECK(js::StringEqualsAscii(lin, "self-hosted")); |
| |
| // Line |
| uint32_t line = 123; |
| result = JS::GetSavedFrameLine(cx, selfHostedFrame, &line, JS::SavedFrameSelfHosted::Exclude); |
| CHECK(result == JS::SavedFrameResult::Ok); |
| CHECK_EQUAL(line, 3U); |
| |
| // Column |
| uint32_t column = 123; |
| result = JS::GetSavedFrameColumn(cx, selfHostedFrame, &column, |
| JS::SavedFrameSelfHosted::Exclude); |
| CHECK(result == JS::SavedFrameResult::Ok); |
| CHECK_EQUAL(column, 5U); |
| |
| // Function display name |
| result = JS::GetSavedFrameFunctionDisplayName(cx, selfHostedFrame, &str, |
| JS::SavedFrameSelfHosted::Exclude); |
| CHECK(result == JS::SavedFrameResult::Ok); |
| lin = str->ensureLinear(cx); |
| CHECK(lin); |
| CHECK(js::StringEqualsAscii(lin, "one")); |
| |
| // Parent |
| JS::RootedObject parent(cx); |
| result = JS::GetSavedFrameParent(cx, 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, parent, &str, JS::SavedFrameSelfHosted::Exclude); |
| CHECK(result == JS::SavedFrameResult::Ok); |
| lin = str->ensureLinear(cx); |
| CHECK(lin); |
| CHECK(js::StringEqualsAscii(lin, "filename.js")); |
| |
| return true; |
| } |
| END_TEST(testSavedStacks_selfHostedFrames) |