diff --git a/js/src/jsapi-tests/testSavedStacks.cpp b/js/src/jsapi-tests/testSavedStacks.cpp index d3bfd75cfafd..b1caa6648507 100644 --- a/js/src/jsapi-tests/testSavedStacks.cpp +++ b/js/src/jsapi-tests/testSavedStacks.cpp @@ -8,8 +8,8 @@ #include "jsfriendapi.h" #include "jsstr.h" +#include "builtin/TestingFunctions.h" #include "jsapi-tests/tests.h" - #include "vm/ArrayObject.h" #include "vm/SavedStacks.h" @@ -63,3 +63,51 @@ BEGIN_TEST(testSavedStacks_ApiDefaultValues) return true; } END_TEST(testSavedStacks_ApiDefaultValues) + +BEGIN_TEST(testSavedStacks_RangeBasedForLoops) +{ + CHECK(js::DefineTestingFunctions(cx, global, 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::Rooted savedFrame(cx, &obj->as()); + + 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 rf(cx, savedFrame); + for (JS::Handle 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) diff --git a/js/src/vm/SavedFrame.h b/js/src/vm/SavedFrame.h index e4fcb08a0ce4..a5773f442c39 100644 --- a/js/src/vm/SavedFrame.h +++ b/js/src/vm/SavedFrame.h @@ -34,15 +34,86 @@ class SavedFrame : public NativeObject { static void finalize(FreeOp* fop, JSObject* obj); // Convenient getters for SavedFrame's reserved slots for use from C++. - JSAtom* getSource(); - uint32_t getLine(); - uint32_t getColumn(); - JSAtom* getFunctionDisplayName(); - JSAtom* getAsyncCause(); - SavedFrame* getParent(); + JSAtom* getSource(); + uint32_t getLine(); + uint32_t getColumn(); + JSAtom* getFunctionDisplayName(); + JSAtom* getAsyncCause(); + SavedFrame* getParent() const; JSPrincipals* getPrincipals(); + bool isSelfHosted(); - bool isSelfHosted(); + // Iterators for use with C++11 range based for loops, eg: + // + // SavedFrame* stack = getSomeSavedFrameStack(); + // for (const SavedFrame* frame : *stack) { + // ... + // } + // + // If you need to keep each frame rooted during iteration, you can use + // `SavedFrame::RootedRange`. Each frame yielded by + // `SavedFrame::RootedRange` is only a valid handle to a rooted `SavedFrame` + // within the loop's block for a single loop iteration. When the next + // iteration begins, the value is invalidated. + // + // RootedSavedFrame stack(cx, getSomeSavedFrameStack()); + // for (HandleSavedFrame frame : SavedFrame::RootedRange(cx, stack)) { + // ... + // } + + class Iterator { + SavedFrame* frame_; + public: + explicit Iterator(SavedFrame* frame) : frame_(frame) { } + SavedFrame& operator*() const { MOZ_ASSERT(frame_); return *frame_; } + bool operator!=(const Iterator& rhs) const { return rhs.frame_ != frame_; } + inline void operator++(); + }; + + Iterator begin() { return Iterator(this); } + Iterator end() { return Iterator(nullptr); } + + class ConstIterator { + const SavedFrame* frame_; + public: + explicit ConstIterator(const SavedFrame* frame) : frame_(frame) { } + const SavedFrame& operator*() const { MOZ_ASSERT(frame_); return *frame_; } + bool operator!=(const ConstIterator& rhs) const { return rhs.frame_ != frame_; } + inline void operator++(); + }; + + ConstIterator begin() const { return ConstIterator(this); } + ConstIterator end() const { return ConstIterator(nullptr); } + + class RootedRange; + + class MOZ_STACK_CLASS RootedIterator { + friend class RootedRange; + RootedRange* range_; + // For use by RootedRange::end() only. + explicit RootedIterator() : range_(nullptr) { } + + public: + explicit RootedIterator(RootedRange& range) : range_(&range) { } + HandleSavedFrame operator*() { MOZ_ASSERT(range_); return range_->frame_; } + bool operator!=(const RootedIterator& rhs) const { + // We should only ever compare to the null range, aka we are just + // testing if this range is done. + MOZ_ASSERT(rhs.range_ == nullptr); + return range_->frame_ != nullptr; + } + inline void operator++(); + }; + + class MOZ_STACK_CLASS RootedRange { + friend class RootedIterator; + RootedSavedFrame frame_; + + public: + RootedRange(JSContext* cx, HandleSavedFrame frame) : frame_(cx, frame) { } + RootedIterator begin() { return RootedIterator(*this); } + RootedIterator end() { return RootedIterator(); } + }; static bool isSavedFrameAndNotProto(JSObject& obj) { return obj.is() && @@ -139,6 +210,25 @@ struct ReconstructedSavedFramePrincipals : public JSPrincipals } }; +inline void +SavedFrame::Iterator::operator++() +{ + frame_ = frame_->getParent(); +} + +inline void +SavedFrame::ConstIterator::operator++() +{ + frame_ = frame_->getParent(); +} + +inline void +SavedFrame::RootedIterator::operator++() +{ + MOZ_ASSERT(range_); + range_->frame_ = range_->frame_->getParent(); +} + } // namespace js namespace JS { diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp index 8b5192c0c236..c4b20c9142e2 100644 --- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -390,7 +390,7 @@ SavedFrame::getAsyncCause() } SavedFrame* -SavedFrame::getParent() +SavedFrame::getParent() const { const Value& v = getReservedSlot(JSSLOT_PARENT); return v.isObject() ? &v.toObject().as() : nullptr;