Bug 1202028 - Implement range based for loops for SavedFrame stacks. r=terrence
authorNick Fitzgerald <fitzgen@gmail.com>
Thu, 10 Sep 2015 16:24:00 +0200
changeset 294527 0e5d2f83896ba64eec134c1eeea9ce3298153f04
parent 294526 aa5459a6703d6f96d45f7d5f58eaf49fec542b65
child 294528 09b2988311894d6badb846fd3177af35a1e2b265
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs1202028
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1202028 - Implement range based for loops for SavedFrame stacks. r=terrence
js/src/jsapi-tests/testSavedStacks.cpp
js/src/vm/SavedFrame.h
js/src/vm/SavedStacks.cpp
--- a/js/src/jsapi-tests/testSavedStacks.cpp
+++ b/js/src/jsapi-tests/testSavedStacks.cpp
@@ -3,18 +3,18 @@
  * 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));
@@ -58,8 +58,56 @@ BEGIN_TEST(testSavedStacks_ApiDefaultVal
 
     // 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));
+
+    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)
--- a/js/src/vm/SavedFrame.h
+++ b/js/src/vm/SavedFrame.h
@@ -29,25 +29,96 @@ class SavedFrame : public NativeObject {
     static bool asyncCauseProperty(JSContext* cx, unsigned argc, Value* vp);
     static bool asyncParentProperty(JSContext* cx, unsigned argc, Value* vp);
     static bool parentProperty(JSContext* cx, unsigned argc, Value* vp);
     static bool toStringMethod(JSContext* cx, unsigned argc, Value* vp);
 
     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<SavedFrame>() &&
                !obj.as<SavedFrame>().getReservedSlot(JSSLOT_SOURCE).isNull();
     }
 
     struct Lookup;
     struct HashPolicy;
@@ -134,16 +205,35 @@ struct ReconstructedSavedFramePrincipals
     // Get the appropriate ReconstructedSavedFramePrincipals singleton for the
     // given JS::ubi::StackFrame that is being reconstructed as a SavedFrame
     // stack.
     static JSPrincipals* getSingleton(JS::ubi::StackFrame& f) {
         return f.isSystem() ? &IsSystem : &IsNotSystem;
     }
 };
 
+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 {
 namespace ubi {
 
 using js::SavedFrame;
 
 // A concrete JS::ubi::StackFrame that is backed by a live SavedFrame object.
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -385,17 +385,17 @@ SavedFrame::getAsyncCause()
     const Value& v = getReservedSlot(JSSLOT_ASYNCCAUSE);
     if (v.isNull())
         return nullptr;
     JSString* s = v.toString();
     return &s->asAtom();
 }
 
 SavedFrame*
-SavedFrame::getParent()
+SavedFrame::getParent() const
 {
     const Value& v = getReservedSlot(JSSLOT_PARENT);
     return v.isObject() ? &v.toObject().as<SavedFrame>() : nullptr;
 }
 
 JSPrincipals*
 SavedFrame::getPrincipals()
 {