Bug 932837 part 3. Make JSStackFrame get information from the JS stack lazily. r=khuey
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 07 Jan 2014 19:53:18 -0500 (2014-01-08)
changeset 162464 e0de03b97222e6ce7d6c2a1c402690eb23ea35f1
parent 162463 a3427a45608df38c83b7d5f68b4a99645f87b14d
child 162465 b6d1eaed0807b5adf618e632dd555cea670932cf
push id25953
push usercbook@mozilla.com
push dateWed, 08 Jan 2014 12:11:30 +0000 (2014-01-08)
treeherdermozilla-central@f8b2a073d930 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs932837
milestone29.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 932837 part 3. Make JSStackFrame get information from the JS stack lazily. r=khuey
dom/base/DOMException.cpp
dom/bindings/Exceptions.cpp
--- a/dom/base/DOMException.cpp
+++ b/dom/base/DOMException.cpp
@@ -146,25 +146,29 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Exception)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Exception)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(Exception)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Exception)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Exception)
   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
   NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mThrownJSVal);
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Exception)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInner)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
   tmp->mThrownJSVal.setNull();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CI_INTERFACE_GETTER1(Exception, nsIXPCException)
 
 Exception::Exception(const char *aMessage,
                      nsresult aResult,
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -227,16 +227,21 @@ public:
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(StackDescriptionOwner)
 
   JS::FrameDescription& FrameAt(size_t aIndex)
   {
     MOZ_ASSERT(aIndex < mDescription->nframes);
     return mDescription->frames[aIndex];
   }
 
+  unsigned NumFrames()
+  {
+    return mDescription->nframes;
+  }
+
 private:
   JS::StackDescription* mDescription;
 };
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(StackDescriptionOwner, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(StackDescriptionOwner, Release)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(StackDescriptionOwner)
@@ -257,62 +262,103 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Sta
       NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDescription->frames[i].fun());
     }
   }
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 class JSStackFrame : public nsIStackFrame
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(JSStackFrame)
   NS_DECL_NSISTACKFRAME
 
-  JSStackFrame();
+  // A null aStackDescription or an aIndex that's out of range for the
+  // number of frames aStackDescription has will mean that the
+  // JSStackFrame will never look at the stack description.  Instead,
+  // it is expected to be initialized by the caller as needed.
+  JSStackFrame(StackDescriptionOwner* aStackDescription, size_t aIndex);
   virtual ~JSStackFrame();
 
   static already_AddRefed<nsIStackFrame>
   CreateStack(JSContext* cx);
   static already_AddRefed<nsIStackFrame>
   CreateStackFrameLocation(uint32_t aLanguage,
                            const char* aFilename,
                            const char* aFunctionName,
                            int32_t aLineNumber,
                            nsIStackFrame* aCaller);
 
 private:
   bool IsJSFrame() const {
     return mLanguage == nsIProgrammingLanguage::JAVASCRIPT;
   }
 
+  const char* GetFilename();
+  const char* GetFunname();
+  int32_t GetLineno();
+
+  nsRefPtr<StackDescriptionOwner> mStackDescription;
   nsCOMPtr<nsIStackFrame> mCaller;
 
+  // Cached values
   char* mFilename;
   char* mFunname;
   int32_t mLineno;
   uint32_t mLanguage;
+
+  size_t mIndex;
+
+  bool mFilenameInitialized;
+  bool mFunnameInitialized;
+  bool mLinenoInitialized;
 };
 
-JSStackFrame::JSStackFrame()
+JSStackFrame::JSStackFrame(StackDescriptionOwner* aStackDescription,
+                           size_t aIndex)
   : mFilename(nullptr),
     mFunname(nullptr),
-    mLineno(0),
-    mLanguage(nsIProgrammingLanguage::UNKNOWN)
-{}
+    mLineno(0)
+{
+  if (aStackDescription && aIndex < aStackDescription->NumFrames()) {
+    mStackDescription = aStackDescription;
+    mIndex = aIndex;
+    mFilenameInitialized = false;
+    mFunnameInitialized = false;
+    mLinenoInitialized = false;
+    mLanguage = nsIProgrammingLanguage::JAVASCRIPT;
+  } else {
+    MOZ_ASSERT(!mStackDescription);
+    mIndex = 0;
+    mFilenameInitialized = true;
+    mFunnameInitialized = true;
+    mLinenoInitialized = true;
+    mLanguage = nsIProgrammingLanguage::UNKNOWN;
+  }
+}
 
 JSStackFrame::~JSStackFrame()
 {
   if (mFilename) {
     nsMemory::Free(mFilename);
   }
   if (mFunname) {
     nsMemory::Free(mFunname);
   }
 }
 
-NS_IMPL_ISUPPORTS1(JSStackFrame, nsIStackFrame)
+NS_IMPL_CYCLE_COLLECTION_2(JSStackFrame, mStackDescription, mCaller)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(JSStackFrame)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(JSStackFrame)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSStackFrame)
+  NS_INTERFACE_MAP_ENTRY(nsIStackFrame)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
 
 /* readonly attribute uint32_t language; */
 NS_IMETHODIMP JSStackFrame::GetLanguage(uint32_t* aLanguage)
 {
   *aLanguage = mLanguage;
   return NS_OK;
 }
 
@@ -326,50 +372,111 @@ NS_IMETHODIMP JSStackFrame::GetLanguageN
     *aLanguageName = (char*) nsMemory::Clone(js, sizeof(js));
   } else {
     *aLanguageName = (char*) nsMemory::Clone(cpp, sizeof(cpp));
   }
 
   return NS_OK;
 }
 
+const char*
+JSStackFrame::GetFilename()
+{
+  if (!mFilenameInitialized) {
+    JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex);
+    if (desc.script()) {
+      AutoJSContext cx;
+      JSAutoCompartment ac(cx, desc.script());
+      const char* filename = JS_GetScriptFilename(cx, desc.script());
+      if (filename) {
+        mFilename =
+          (char*)nsMemory::Clone(filename, sizeof(char)*(strlen(filename)+1));
+      }
+    }
+    mFilenameInitialized = true;
+  }
+
+  return mFilename;
+}
+
 /* readonly attribute string filename; */
 NS_IMETHODIMP JSStackFrame::GetFilename(char** aFilename)
 {
   NS_ENSURE_ARG_POINTER(aFilename);
 
-  if (mFilename) {
-    *aFilename = (char*) nsMemory::Clone(mFilename,
-                                         sizeof(char)*(strlen(mFilename)+1));
+  const char* filename = GetFilename();
+  if (filename) {
+    *aFilename = (char*) nsMemory::Clone(filename,
+                                         sizeof(char)*(strlen(filename)+1));
   } else {
     *aFilename = nullptr;
   }
 
   return NS_OK;
 }
 
+const char*
+JSStackFrame::GetFunname()
+{
+  if (!mFunnameInitialized) {
+    JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex);
+    if (desc.fun() && desc.script()) {
+      AutoJSContext cx;
+      JSAutoCompartment ac(cx, desc.script());
+      JS::Rooted<JSFunction*> fun(cx, desc.fun());
+      JS::Rooted<JSString*> funid(cx, JS_GetFunctionDisplayId(fun));
+      if (funid) {
+        size_t length = JS_GetStringEncodingLength(cx, funid);
+        if (length != size_t(-1)) {
+          mFunname = static_cast<char *>(nsMemory::Alloc(length + 1));
+          if (mFunname) {
+            JS_EncodeStringToBuffer(cx, funid, mFunname, length);
+            mFunname[length] = '\0';
+          }
+        }
+      }
+    }
+    mFunnameInitialized = true;
+  }
+
+  return mFunname;
+}
+
 /* readonly attribute string name; */
 NS_IMETHODIMP JSStackFrame::GetName(char** aFunction)
 {
   NS_ENSURE_ARG_POINTER(aFunction);
 
-  if (mFunname) {
-    *aFunction = (char*) nsMemory::Clone(mFunname,
-                                         sizeof(char)*(strlen(mFunname)+1));
+  const char* funname = GetFunname();
+  if (funname) {
+    *aFunction = (char*) nsMemory::Clone(funname,
+                                         sizeof(char)*(strlen(funname)+1));
   } else {
     *aFunction = nullptr;
   }
 
   return NS_OK;
 }
 
+int32_t
+JSStackFrame::GetLineno()
+{
+  if (!mLinenoInitialized) {
+    JS::FrameDescription& desc = mStackDescription->FrameAt(mIndex);
+    mLineno = desc.lineno();
+    mLinenoInitialized = true;
+  }
+
+  return mLineno;
+}
+
 /* readonly attribute int32_t lineNumber; */
 NS_IMETHODIMP JSStackFrame::GetLineNumber(int32_t* aLineNumber)
 {
-  *aLineNumber = mLineno;
+  *aLineNumber = GetLineno();
   return NS_OK;
 }
 
 /* readonly attribute string sourceLine; */
 NS_IMETHODIMP JSStackFrame::GetSourceLine(char** aSourceLine)
 {
   *aSourceLine = nullptr;
   return NS_OK;
@@ -381,87 +488,75 @@ NS_IMETHODIMP JSStackFrame::GetCaller(ns
   NS_IF_ADDREF(*aCaller = mCaller);
   return NS_OK;
 }
 
 /* string toString (); */
 NS_IMETHODIMP JSStackFrame::ToString(char** _retval)
 {
   const char* frametype = IsJSFrame() ? "JS" : "native";
-  const char* filename = mFilename ? mFilename : "<unknown filename>";
-  const char* funname = mFunname ? mFunname : "<TOP_LEVEL>";
+  const char* filename = GetFilename();
+  if (!filename) {
+    filename = "<unknown filename>";
+  }
+  const char* funname = GetFunname();
+  if (!funname) {
+    funname = "<TOP_LEVEL>";
+  }
   static const char format[] = "%s frame :: %s :: %s :: line %d";
   int len = sizeof(char)*
               (strlen(frametype) + strlen(filename) + strlen(funname)) +
             sizeof(format) + 3 * sizeof(mLineno);
 
   char* buf = (char*) nsMemory::Alloc(len);
-  JS_snprintf(buf, len, format, frametype, filename, funname, mLineno);
+  JS_snprintf(buf, len, format, frametype, filename, funname, GetLineno());
   *_retval = buf;
   return NS_OK;
 }
 
 /* static */ already_AddRefed<nsIStackFrame>
 JSStackFrame::CreateStack(JSContext* cx)
 {
   static const unsigned MAX_FRAMES = 100;
 
-  nsRefPtr<JSStackFrame> first = new JSStackFrame();
-  nsRefPtr<JSStackFrame> self = first;
-
   JS::StackDescription* desc = JS::DescribeStack(cx, MAX_FRAMES);
   if (!desc) {
     return nullptr;
   }
 
   nsRefPtr<StackDescriptionOwner> descOwner = new StackDescriptionOwner(desc);
 
-  for (size_t i = 0; i < desc->nframes && self; i++) {
-    self->mLanguage = nsIProgrammingLanguage::JAVASCRIPT;
+  nsRefPtr<JSStackFrame> first;
+  nsRefPtr<JSStackFrame> last;
 
-    JSAutoCompartment ac(cx, desc->frames[i].script());
-    const char* filename = JS_GetScriptFilename(cx, desc->frames[i].script());
-    if (filename) {
-      self->mFilename =
-        (char*)nsMemory::Clone(filename, sizeof(char)*(strlen(filename)+1));
-    }
-
-    self->mLineno = desc->frames[i].lineno();
+  // We create one dummy frame at the end (why?), so go up through
+  // desc->nframes+1
+  for (size_t i = 0; i < desc->nframes+1; i++) {
+    nsRefPtr<JSStackFrame> frame = new JSStackFrame(descOwner, i);
 
-    JSFunction* fun = desc->frames[i].fun();
-    if (fun) {
-      JS::Rooted<JSString*> funid(cx, JS_GetFunctionDisplayId(fun));
-      if (funid) {
-        size_t length = JS_GetStringEncodingLength(cx, funid);
-        if (length != size_t(-1)) {
-          self->mFunname = static_cast<char *>(nsMemory::Alloc(length + 1));
-          if (self->mFunname) {
-            JS_EncodeStringToBuffer(cx, funid, self->mFunname, length);
-            self->mFunname[length] = '\0';
-          }
-        }
-      }
+    if (last) {
+      last->mCaller = frame;
+    } else {
+      MOZ_ASSERT(!first, "How can we have a first but not a last?");
+      first = frame;
     }
-
-    nsRefPtr<JSStackFrame> frame = new JSStackFrame();
-    self->mCaller = frame;
-    self.swap(frame);
+    last.swap(frame);
   }
 
   return first.forget();
 }
 
 /* static */ already_AddRefed<nsIStackFrame>
 JSStackFrame::CreateStackFrameLocation(uint32_t aLanguage,
                                        const char* aFilename,
                                        const char* aFunctionName,
                                        int32_t aLineNumber,
                                        nsIStackFrame* aCaller)
 {
-  nsRefPtr<JSStackFrame> self = new JSStackFrame();
+  nsRefPtr<JSStackFrame> self = new JSStackFrame(nullptr, 0);
 
   self->mLanguage = aLanguage;
   self->mLineno = aLineNumber;
 
   if (aFilename) {
     self->mFilename =
       (char*)nsMemory::Clone(aFilename, sizeof(char)*(strlen(aFilename)+1));
   }