Bug 662646 - GC topcrash diagnostics [needs backout before Aurora merge!] (r=dmandelin)
authorBill McCloskey <wmccloskey@mozilla.com>
Thu, 07 Jul 2011 17:31:24 -0700
changeset 73315 3e076388bd13d7926a42cfb32f0c924017064281
parent 73314 d029b8f7ba79c4d7b5db2de7ebb2ea5d20454ea6
child 73316 1433eaa829ecd035ff6671fc0c6ae1206751b9a3
push id235
push userbzbarsky@mozilla.com
push dateTue, 27 Sep 2011 17:13:04 +0000
treeherdermozilla-beta@2d1e082d176a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdmandelin
bugs662646
milestone8.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 662646 - GC topcrash diagnostics [needs backout before Aurora merge!] (r=dmandelin)
js/src/jscntxt.h
js/src/jscrashformat.h
js/src/jscrashreport.cpp
js/src/jsgc.cpp
js/src/jsgcmark.cpp
js/src/jsgcmark.h
js/src/jsproxy.cpp
js/src/jsutil.h
js/src/jswrapper.cpp
js/src/jswrapper.h
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -408,16 +408,22 @@ struct JSRuntime {
      * gcTriggerCompartment is reset to NULL and a global GC is performed.
      */
     JSCompartment       *gcTriggerCompartment;
 
     /* Compartment that is currently involved in per-compartment GC */
     JSCompartment       *gcCurrentCompartment;
 
     /*
+     * If this is non-NULL, all marked objects must belong to this compartment.
+     * This is used to look for compartment bugs.
+     */
+    JSCompartment       *gcCheckCompartment;
+
+    /*
      * We can pack these flags as only the GC thread writes to them. Atomic
      * updates to packed bytes are not guaranteed, so stores issued by one
      * thread may be lost due to unsynchronized read-modify-write cycles on
      * other threads.
      */
     bool                gcPoke;
     bool                gcMarkAndSweep;
     bool                gcRunning;
--- a/js/src/jscrashformat.h
+++ b/js/src/jscrashformat.h
@@ -95,12 +95,17 @@ struct CrashRing
 {
     CrashRing(uint64 id) : header(id), offset(0) { memset(buffer, 0, sizeof(buffer)); }
 
     CrashHeader header;
     uint64 offset; /* Next byte to be written in the buffer. */
     char buffer[crash_buffer_size];
 };
 
+/* These are the tag values for each entry in the CrashRing. */
+enum {
+    JS_CRASH_TAG_GC = 0x200
+};
+
 } /* namespace crash */
 } /* namespace js */
 
 #endif
--- a/js/src/jscrashreport.cpp
+++ b/js/src/jscrashreport.cpp
@@ -263,17 +263,17 @@ js_SaveCrashData(uint64 tag, void *ptr, 
 {
     if (gInitialized)
         gRingBuffer.push(tag, ptr, size);
 }
 
 JS_PUBLIC_API(void)
 JS_EnumerateDiagnosticMemoryRegions(JSEnumerateDiagnosticMemoryCallback callback)
 {
-#if 0
+#if 1
     if (!gInitialized) {
         gInitialized = true;
         (*callback)(&gGCStack, sizeof(gGCStack));
         (*callback)(&gErrorStack, sizeof(gErrorStack));
         (*callback)(&gRingBuffer, sizeof(gRingBuffer));
     }
 #endif
 }
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -55,16 +55,18 @@
 #include "jsutil.h"
 #include "jshash.h"
 #include "jsbit.h"
 #include "jsclist.h"
 #include "jsprf.h"
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscompartment.h"
+#include "jscrashreport.h"
+#include "jscrashformat.h"
 #include "jscntxt.h"
 #include "jsversion.h"
 #include "jsdbgapi.h"
 #include "jsexn.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jsgcchunk.h"
 #include "jsgcmark.h"
@@ -234,19 +236,17 @@ Arena::finalize(JSContext *cx)
                     newListTail->end = thing - sizeof(T);
                     newListTail = newListTail->nextSpanUnchecked();
                     newFreeSpanStart = 0;
                 }
             } else {
                 if (!newFreeSpanStart)
                     newFreeSpanStart = thing;
                 t->finalize(cx);
-#ifdef DEBUG
                 memset(t, JS_FREE_PATTERN, sizeof(T));
-#endif
             }
         }
     }
 
     if (allClear) {
         JS_ASSERT(newListTail == &newListHead);
         JS_ASSERT(newFreeSpanStart == thingsStart(sizeof(T)));
         return true;
@@ -2657,16 +2657,22 @@ GCCycle(JSContext *cx, JSCompartment *co
     rt->setGCLastBytes(rt->gcBytes, gckind);
     rt->gcCurrentCompartment = NULL;
     rt->gcWeakMapList = NULL;
 
     for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
         (*c)->setGCLastBytes((*c)->gcBytes, gckind);
 }
 
+struct GCCrashData
+{
+    int isRegen;
+    int isCompartment;
+};
+
 void
 js_GC(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind)
 {
     JSRuntime *rt = cx->runtime;
 
     /*
      * Don't collect garbage if the runtime isn't up, and cx is not the last
      * context in the runtime.  The last context must force a GC, and nothing
@@ -2678,16 +2684,21 @@ js_GC(JSContext *cx, JSCompartment *comp
 
     if (JS_ON_TRACE(cx)) {
         JS_ASSERT(gckind != GC_LAST_CONTEXT);
         return;
     }
 
     RecordNativeStackTopForGC(cx);
 
+    GCCrashData crashData;
+    crashData.isRegen = rt->shapeGen & SHAPE_OVERFLOW_BIT;
+    crashData.isCompartment = !!comp;
+    js_SaveCrashData(crash::JS_CRASH_TAG_GC, &crashData, sizeof(crashData));
+
     GCTIMER_BEGIN(rt, comp);
 
     do {
         /*
          * Let the API user decide to defer a GC if it wants to (unless this
          * is the last context).  Invoke the callback regardless. Sample the
          * callback in case we are freely racing with a JS_SetGCCallback{,RT}
          * on another thread.
@@ -2716,16 +2727,18 @@ js_GC(JSContext *cx, JSCompartment *comp
          * stop creating garbage.
          */
     } while (gckind == GC_LAST_CONTEXT && rt->gcPoke);
 
     rt->gcNextFullGCTime = PRMJ_Now() + GC_IDLE_FULL_SPAN;
 
     rt->gcChunkAllocationSinceLastGC = false;
     GCTIMER_END(gckind == GC_LAST_CONTEXT);
+
+    js_SnapshotGCStack();
 }
 
 namespace js {
 
 class AutoCopyFreeListToArenas {
     JSRuntime *rt;
 
   public:
--- a/js/src/jsgcmark.cpp
+++ b/js/src/jsgcmark.cpp
@@ -110,16 +110,22 @@ Mark(JSTracer *trc, T *thing)
 
     JS_ASSERT(!JSAtom::isStatic(thing));
     JS_ASSERT(thing->isAligned());
 
     JSRuntime *rt = trc->context->runtime;
     JS_ASSERT(thing->arenaHeader()->compartment);
     JS_ASSERT(thing->arenaHeader()->compartment->rt == rt);
 
+    if (rt->gcCheckCompartment && thing->compartment() != rt->gcCheckCompartment &&
+        thing->compartment() != rt->atomsCompartment)
+    {
+        JS_Assert("compartment mismatch in GC", __FILE__, __LINE__);
+    }
+
     /*
      * Don't mark things outside a compartment if we are in a per-compartment
      * GC.
      */
     if (!rt->gcCurrentCompartment || thing->compartment() == rt->gcCurrentCompartment) {
         if (IS_GC_MARKING_TRACER(trc))
             PushMarkStack(static_cast<GCMarker *>(trc), thing);
         else
@@ -154,16 +160,26 @@ MarkObject(JSTracer *trc, JSObject &obj,
 {
     JS_ASSERT(trc);
     JS_ASSERT(&obj);
     JS_SET_TRACING_NAME(trc, name);
     Mark(trc, &obj);
 }
 
 void
+MarkCrossCompartmentObject(JSTracer *trc, JSObject &obj, const char *name)
+{
+    JSRuntime *rt = trc->context->runtime;
+    if (rt->gcCurrentCompartment && rt->gcCurrentCompartment != obj.compartment())
+        return;
+
+    MarkObject(trc, obj, name);
+}
+
+void
 MarkObjectWithPrinter(JSTracer *trc, JSObject &obj, JSTraceNamePrinter printer,
 		      const void *arg, size_t index)
 {
     JS_ASSERT(trc);
     JS_ASSERT(&obj);
     JS_SET_TRACING_DETAILS(trc, printer, arg, index);
     Mark(trc, &obj);
 }
@@ -347,16 +363,32 @@ MarkValueRaw(JSTracer *trc, const js::Va
 void
 MarkValue(JSTracer *trc, const js::Value &v, const char *name)
 {
     JS_SET_TRACING_NAME(trc, name);
     MarkValueRaw(trc, v);
 }
 
 void
+MarkCrossCompartmentValue(JSTracer *trc, const js::Value &v, const char *name)
+{
+    if (v.isMarkable()) {
+        js::gc::Cell *cell = (js::gc::Cell *)v.toGCThing();
+        unsigned kind = v.gcKind();
+        if (kind == JSTRACE_STRING && ((JSString *)cell)->isStaticAtom())
+            return;
+        JSRuntime *rt = trc->context->runtime;
+        if (rt->gcCurrentCompartment && cell->compartment() != rt->gcCurrentCompartment)
+            return;
+
+        MarkValue(trc, v, name);
+    }
+}
+
+void
 MarkValueRange(JSTracer *trc, Value *beg, Value *end, const char *name)
 {
     for (Value *vp = beg; vp < end; ++vp) {
         JS_SET_TRACING_INDEX(trc, name, vp - beg);
         MarkValueRaw(trc, *vp);
     }
 }
 
@@ -743,16 +775,19 @@ MarkChildren(JSTracer *trc, JSXML *xml)
 }
 #endif
 
 } /* namespace gc */
 
 void
 GCMarker::drainMarkStack()
 {
+    JSRuntime *rt = context->runtime;
+    rt->gcCheckCompartment = rt->gcCurrentCompartment;
+
     while (!isMarkStackEmpty()) {
         while (!ropeStack.isEmpty())
             ScanRope(this, ropeStack.pop());
 
         while (!objStack.isEmpty())
             ScanObject(this, objStack.pop());
 
         while (!xmlStack.isEmpty())
@@ -767,16 +802,18 @@ GCMarker::drainMarkStack()
         if (isMarkStackEmpty()) {
             /*
              * Mark children of things that caused too deep recursion during the above
              * tracing. Don't do this until we're done with everything else.
              */
             markDelayedChildren();
         }
     }
+
+    rt->gcCheckCompartment = NULL;
 }
 
 } /* namespace js */
 
 JS_PUBLIC_API(void)
 JS_TraceChildren(JSTracer *trc, void *thing, uint32 kind)
 {
     switch (kind) {
--- a/js/src/jsgcmark.h
+++ b/js/src/jsgcmark.h
@@ -57,16 +57,23 @@ void
 MarkString(JSTracer *trc, JSString *str);
 
 void
 MarkString(JSTracer *trc, JSString *str, const char *name);
 
 void
 MarkObject(JSTracer *trc, JSObject &obj, const char *name);
 
+/*
+ * Mark an object that may be in a different compartment from the compartment
+ * being GC'd. (Although it won't be marked if it's in the wrong compartment.)
+ */
+void
+MarkCrossCompartmentObject(JSTracer *trc, JSObject &obj, const char *name);
+
 void
 MarkObjectWithPrinter(JSTracer *trc, JSObject &obj, JSTraceNamePrinter printer,
 		      const void *arg, size_t index);
 
 void
 MarkShape(JSTracer *trc, const Shape *shape, const char *name);
 
 void
@@ -97,16 +104,23 @@ void
 MarkKind(JSTracer *trc, void *thing, uint32 kind);
 
 void
 MarkValueRaw(JSTracer *trc, const js::Value &v);
 
 void
 MarkValue(JSTracer *trc, const js::Value &v, const char *name);
 
+/*
+ * Mark a value that may be in a different compartment from the compartment
+ * being GC'd. (Although it won't be marked if it's in the wrong compartment.)
+ */
+void
+MarkCrossCompartmentValue(JSTracer *trc, const js::Value &v, const char *name);
+
 void
 MarkValueRange(JSTracer *trc, Value *beg, Value *end, const char *name);
 
 void
 MarkValueRange(JSTracer *trc, size_t len, Value *vec, const char *name);
 
 void
 MarkShapeRange(JSTracer *trc, const Shape **beg, const Shape **end, const char *name);
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -975,30 +975,30 @@ proxy_DeleteProperty(JSContext *cx, JSOb
     rval->setBoolean(deleted);
     return true;
 }
 
 static void
 proxy_TraceObject(JSTracer *trc, JSObject *obj)
 {
     obj->getProxyHandler()->trace(trc, obj);
-    MarkValue(trc, obj->getProxyPrivate(), "private");
-    MarkValue(trc, obj->getProxyExtra(), "extra");
+    MarkCrossCompartmentValue(trc, obj->getProxyPrivate(), "private");
+    MarkCrossCompartmentValue(trc, obj->getProxyExtra(), "extra");
     if (obj->isFunctionProxy()) {
-        MarkValue(trc, GetCall(obj), "call");
-        MarkValue(trc, GetConstruct(obj), "construct");
+        MarkCrossCompartmentValue(trc, GetCall(obj), "call");
+        MarkCrossCompartmentValue(trc, GetConstruct(obj), "construct");
     }
 }
 
 static void
 proxy_TraceFunction(JSTracer *trc, JSObject *obj)
 {
     proxy_TraceObject(trc, obj);
-    MarkValue(trc, GetCall(obj), "call");
-    MarkValue(trc, GetConstruct(obj), "construct");
+    MarkCrossCompartmentValue(trc, GetCall(obj), "call");
+    MarkCrossCompartmentValue(trc, GetConstruct(obj), "construct");
 }
 
 static JSBool
 proxy_Convert(JSContext *cx, JSObject *proxy, JSType hint, Value *vp)
 {
     JS_ASSERT(proxy->isProxy());
     return JSProxy::defaultValue(cx, proxy, hint, vp);
 }
--- a/js/src/jsutil.h
+++ b/js/src/jsutil.h
@@ -40,30 +40,33 @@
 /*
  * PR assertion checker.
  */
 
 #ifndef jsutil_h___
 #define jsutil_h___
 
 #include "jstypes.h"
+#include "jscrashreport.h"
 #include "mozilla/Util.h"
 #include <stdlib.h>
 #include <string.h>
 
 JS_BEGIN_EXTERN_C
 
 #define JS_CRASH_UNLESS(__cond)                                                 \
     JS_BEGIN_MACRO                                                              \
         if (!(__cond)) {                                                        \
             *(int *)(uintptr_t)0xccadbeef = 0;                                  \
             ((void(*)())0)(); /* More reliable, but doesn't say CCADBEEF */     \
         }                                                                       \
     JS_END_MACRO
 
+#define JS_FREE_PATTERN 0xDA
+
 #ifdef DEBUG
 
 #define JS_ASSERT(expr)                                                       \
     ((expr) ? (void)0 : JS_Assert(#expr, __FILE__, __LINE__))
 
 #define JS_ASSERT_IF(cond, expr)                                              \
     ((!(cond) || (expr)) ? (void)0 : JS_Assert(#expr, __FILE__, __LINE__))
 
@@ -75,18 +78,16 @@ JS_BEGIN_EXTERN_C
 #define JS_ALWAYS_FALSE(expr) JS_ASSERT(!(expr))
 
 # ifdef JS_THREADSAFE
 # define JS_THREADSAFE_ASSERT(expr) JS_ASSERT(expr) 
 # else
 # define JS_THREADSAFE_ASSERT(expr) ((void) 0)
 # endif
 
-#define JS_FREE_PATTERN 0xDA
-
 #else
 
 #define JS_ASSERT(expr)         ((void) 0)
 #define JS_ASSERT_IF(cond,expr) ((void) 0)
 #define JS_NOT_REACHED(reason)
 #define JS_ALWAYS_TRUE(expr)    ((void) (expr))
 #define JS_ALWAYS_FALSE(expr)    ((void) (expr))
 #define JS_THREADSAFE_ASSERT(expr) ((void) 0)
@@ -215,34 +216,40 @@ extern JS_PUBLIC_DATA(JSUint32) OOM_coun
             return NULL; \
         } \
     } while (0)
 
 #else
 #define JS_OOM_POSSIBLY_FAIL() do {} while(0)
 #endif
 
+static JS_INLINE void *js_record_oom(void *p) {
+    if (!p)
+        js_SnapshotErrorStack();
+    return p;
+}
+
 /*
  * SpiderMonkey code should not be calling these allocation functions directly.
  * Instead, all calls should go through JSRuntime, JSContext or OffTheBooks.
  * However, js_free() can be called directly.
  */
 static JS_INLINE void* js_malloc(size_t bytes) {
     JS_OOM_POSSIBLY_FAIL();
-    return malloc(bytes);
+    return js_record_oom(malloc(bytes));
 }
 
 static JS_INLINE void* js_calloc(size_t bytes) {
     JS_OOM_POSSIBLY_FAIL();
-    return calloc(bytes, 1);
+    return js_record_oom(calloc(bytes, 1));
 }
 
 static JS_INLINE void* js_realloc(void* p, size_t bytes) {
     JS_OOM_POSSIBLY_FAIL();
-    return realloc(p, bytes);
+    return js_record_oom(realloc(p, bytes));
 }
 
 static JS_INLINE void js_free(void* p) {
     free(p);
 }
 #endif/* JS_USE_CUSTOM_ALLOCATOR */
 
 JS_END_EXTERN_C
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -763,9 +763,15 @@ JSCrossCompartmentWrapper::defaultValue(
 
     if (!JSWrapper::defaultValue(cx, wrapper, hint, vp))
         return false;
 
     call.leave();
     return call.origin->wrap(cx, vp);
 }
 
+void
+JSCrossCompartmentWrapper::trace(JSTracer *trc, JSObject *wrapper)
+{
+    MarkCrossCompartmentObject(trc, *wrappedObject(wrapper), "wrappedObject");
+}
+
 JSCrossCompartmentWrapper JSCrossCompartmentWrapper::singleton(0u);
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -148,16 +148,18 @@ class JS_FRIEND_API(JSCrossCompartmentWr
     virtual bool call(JSContext *cx, JSObject *wrapper, uintN argc, js::Value *vp);
     virtual bool construct(JSContext *cx, JSObject *wrapper,
                            uintN argc, js::Value *argv, js::Value *rval);
     virtual bool hasInstance(JSContext *cx, JSObject *wrapper, const js::Value *vp, bool *bp);
     virtual JSString *obj_toString(JSContext *cx, JSObject *wrapper);
     virtual JSString *fun_toString(JSContext *cx, JSObject *wrapper, uintN indent);
     virtual bool defaultValue(JSContext *cx, JSObject *wrapper, JSType hint, js::Value *vp);
 
+    virtual void trace(JSTracer *trc, JSObject *wrapper);
+
     static JSCrossCompartmentWrapper singleton;
 };
 
 namespace js {
 
 // A hacky class that lets a friend force a fake frame. We must already be
 // in the compartment of |target| when we enter the forced frame.
 class JS_FRIEND_API(ForceFrame)