Implement the newScript hook. Also, change the shell builtin clone() function to support cloning a function into a different compartment (for use by one of the tests).
authorJason Orendorff <jorendorff@mozilla.com>
Thu, 07 Jul 2011 00:34:59 -0500
changeset 74502 93e47625c3ed8cc43095b86847512b516d5dceb5
parent 74501 d3b7501084d11b4d067596d6002694430b8b8a6b
child 74503 53d431ddc5ac1ea56d9221fa29a4154c13ab553b
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
milestone7.0a1
Implement the newScript hook. Also, change the shell builtin clone() function to support cloning a function into a different compartment (for use by one of the tests).
js/src/jit-test/tests/debug/Frame-02.js
js/src/jit-test/tests/debug/Frame-03.js
js/src/jit-test/tests/debug/hooks-newScript-01.js
js/src/jit-test/tests/debug/hooks-newScript-02.js
js/src/jsapi-tests/testDebugger.cpp
js/src/jsapi.cpp
js/src/jsdbg.cpp
js/src/jsdbg.h
js/src/jsemit.h
js/src/jsfun.cpp
js/src/jsparse.cpp
js/src/jsscript.cpp
js/src/jsxdrapi.cpp
js/src/shell/js.cpp
--- a/js/src/jit-test/tests/debug/Frame-02.js
+++ b/js/src/jit-test/tests/debug/Frame-02.js
@@ -1,10 +1,10 @@
 // When the debugger is triggered twice from the same stack frame, the same
-// Debugger.Frame object must be passed to the hook both times.
+// Debugger.Frame object is passed to the hook both times.
 
 var g = newGlobal('new-compartment');
 var hits, frame;
 var dbg = Debugger(g);
 dbg.hooks = {
     debuggerHandler: function (f) {
         if (hits++ == 0)
             frame = f;
--- a/js/src/jit-test/tests/debug/Frame-03.js
+++ b/js/src/jit-test/tests/debug/Frame-03.js
@@ -1,10 +1,10 @@
 // When the debugger is triggered from different stack frames that happen to
-// occupy the same memory, it must deliver different Debugger.Frame objects.
+// occupy the same memory, it delivers different Debugger.Frame objects.
 
 var g = newGlobal('new-compartment');
 var dbg = Debugger(g);
 var hits;
 var a = [];
 dbg.hooks = {
     debuggerHandler: function (frame) {
         for (var i = 0; i < a.length; i++) 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/hooks-newScript-01.js
@@ -0,0 +1,42 @@
+// Basic newScript hook tests.
+
+var g = newGlobal('new-compartment');
+var dbg = Debugger(g);
+var seen = WeakMap();
+var hits = 0;
+dbg.hooks = {
+    newScript: function (s) {
+	assertEq(s instanceof Debugger.Script, true);
+	assertEq(!seen.has(s), true);
+	seen.set(s, true);
+	hits++;
+    }
+};
+
+// eval code
+hits = 0;
+assertEq(g.eval("2 + 2"), 4);
+assertEq(hits, 1);
+
+hits = 0;
+assertEq(g.eval("eval('2 + 3')"), 5);
+assertEq(hits, 2);
+
+// global code
+hits = 0;
+g.evaluate("3 + 4");
+assertEq(hits, 1);
+
+// function code
+hits = 0;
+var fn = g.Function("a", "return 5 + a;");
+assertEq(hits, 1);
+assertEq(fn(8), 13);
+assertEq(hits, 1);
+
+// cloning functions across compartments
+var g2 = newGlobal('new-compartment');
+dbg.addDebuggee(g2, dbg);
+hits = 0;
+g2.clone(fn);
+assertEq(hits, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/hooks-newScript-02.js
@@ -0,0 +1,65 @@
+// Creating a new script with any number of subscripts triggers the newScript hook exactly once.
+
+var g = newGlobal('new-compartment');
+var dbg = Debugger(g);
+var seen = WeakMap();
+var hits;
+dbg.hooks = {
+    newScript: function (s) {
+	assertEq(s instanceof Debugger.Script, true);
+	assertEq(!seen.has(s), true);
+	seen.set(s, true);
+	hits++;
+    }
+};
+
+function test(f) {
+    hits = 0;
+    f();
+    assertEq(hits, 1);
+}
+
+// eval declaring a function
+test(function () { g.eval("function A(m, n) { return m===0?n+1:n===0?A(m-1,1):A(m-1,A(m,n-1)); }"); });
+
+// evaluate declaring a function
+test(function () { g.eval("function g(a, b) { return b===0?a:g(b,a%b); }"); });
+
+// eval declaring multiple functions
+test(function () {
+    g.eval("function e(i) { return i===0||o(i-1); }\n" +
+	   "function o(i) { return i!==0&&e(i-1); }\n");
+});
+
+// eval declaring nested functions
+test(function () { g.eval("function plus(x) { return function plusx(y) { return x + y; }; }"); });
+
+// eval with a function-expression
+test(function () { g.eval("[3].map(function (i) { return -i; });"); });
+
+// eval with getters and setters
+test(function () { g.eval("var obj = {get x() { return 1; }, set x(v) { print(v); }};"); });
+
+// Function with nested functions
+test(function () { return g.Function("a", "b", "return b - a;"); });
+
+// cloning a function with nested functions
+test(function () { g.clone(Function("x", "return x + 1")); });
+
+// eval declaring a generator
+test(function () { g.eval("function r(n) { for (var i=0;i<n;i++) yield i; }"); });
+
+// eval with a generator-expression
+test(function () { g.eval("var it = (obj[p] for (p in obj));"); });
+
+// eval creating several instances of a closure
+test(function () { g.eval("for (var i = 0; i < 7; i++)\n" +
+			  "    obj = function () { return obj; };\n"); });
+
+// non-strict-mode direct eval
+g.eval("function e(s) { eval(s); }");
+test(function () { g.e("function f(x) { return -x; }"); });
+
+// strict-mode direct eval
+g.eval("function E(s) { 'use strict'; eval(s); }");
+test(function () { g.E("function g(x) { return -x; }"); });
--- a/js/src/jsapi-tests/testDebugger.cpp
+++ b/js/src/jsapi-tests/testDebugger.cpp
@@ -139,8 +139,79 @@ BEGIN_TEST(testDebugger_debuggerObjectVs
     EVAL("debuggee.eval('debugger; debugger; debugger;');\n"
          "hits;\n",
          &v);
     CHECK_SAME(v, INT_TO_JSVAL(4));
     
     return true;
 }
 END_TEST(testDebugger_debuggerObjectVsDebugMode)
+
+BEGIN_TEST(testDebugger_newScriptHook)
+{
+    // Test that top-level indirect eval fires the newScript hook.
+    CHECK(JS_DefineDebuggerObject(cx, global));
+    JSObject *g1, *g2;
+    g1 = JS_NewCompartmentAndGlobalObject(cx, getGlobalClass(), NULL);
+    CHECK(g1);
+    {
+        JSAutoEnterCompartment ae;
+        CHECK(ae.enter(cx, g1));
+        CHECK(JS_InitStandardClasses(cx, g1));
+        g2 = JS_NewGlobalObject(cx, getGlobalClass());
+        CHECK(g2);
+        CHECK(JS_InitStandardClasses(cx, g2));
+    }
+
+    JSObject *g1Wrapper = g1;
+    CHECK(JS_WrapObject(cx, &g1Wrapper));
+    jsval v = OBJECT_TO_JSVAL(g1Wrapper);
+    CHECK(JS_SetProperty(cx, global, "g1", &v));
+
+    JSObject *g2Wrapper = g2;
+    CHECK(JS_WrapObject(cx, &g2Wrapper));
+    v = OBJECT_TO_JSVAL(g2Wrapper);
+    CHECK(JS_SetProperty(cx, global, "g2", &v));
+
+    EXEC("var dbg = Debugger(g1);\n"
+         "var hits = 0;\n"
+         "dbg.hooks = {\n"
+         "    newScript: function (s) {\n"
+         "        hits += Number(s instanceof Debugger.Script);\n"
+         "    }\n"
+         "};\n");
+
+    // Since g1 is a debuggee and g2 is not, g1.eval should trigger newScript
+    // and g2.eval should not, regardless of what scope object we use to enter
+    // the compartment.
+    //
+    // (Not all scripts are permanently associated with specific global
+    // objects, but eval scripts are, so we deliver them only to debuggers that
+    // are watching that particular global.)
+    //
+    bool ok = true;
+    ok = ok && testIndirectEval(g1, g1, "Math.abs(0)", 1);
+    ok = ok && testIndirectEval(g2, g1, "Math.abs(1)", 1);
+    ok = ok && testIndirectEval(g1, g2, "Math.abs(-1)", 0);
+    ok = ok && testIndirectEval(g2, g2, "Math.abs(-2)", 0);
+    return ok;
+}
+
+bool testIndirectEval(JSObject *scope, JSObject *g, const char *code, int expectedHits)
+{
+    EXEC("hits = 0;");
+
+    {
+        JSAutoEnterCompartment ae;
+        CHECK(ae.enter(cx, scope));
+        JSString *codestr = JS_NewStringCopyZ(cx, code);
+        CHECK(codestr);
+        jsval argv[1] = { STRING_TO_JSVAL(codestr) };
+        jsval v;
+        CHECK(JS_CallFunctionName(cx, g, "eval", 1, argv, &v));
+    }
+
+    jsval hitsv;
+    EVAL("hits", &hitsv);
+    CHECK_SAME(hitsv, INT_TO_JSVAL(expectedHits));
+    return true;
+}
+END_TEST(testDebugger_newScriptHook)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4455,27 +4455,22 @@ static JSObject *
 CompileUCScriptForPrincipalsCommon(JSContext *cx, JSObject *obj, JSPrincipals *principals,
                                       const jschar *chars, size_t length,
                                       const char *filename, uintN lineno, JSVersion version)
 {
     JS_THREADSAFE_ASSERT(cx->compartment != cx->runtime->atomsCompartment);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj, principals);
 
-    uint32 tcflags = JS_OPTIONS_TO_TCFLAGS(cx) | TCF_NEED_MUTABLE_SCRIPT;
+    uint32 tcflags = JS_OPTIONS_TO_TCFLAGS(cx) | TCF_NEED_MUTABLE_SCRIPT | TCF_NEED_SCRIPT_OBJECT;
     JSScript *script = Compiler::compileScript(cx, obj, NULL, principals, tcflags,
                                                chars, length, filename, lineno, version);
-    JSObject *scriptObj = NULL;
-    if (script) {
-        scriptObj = js_NewScriptObject(cx, script);
-        if (!scriptObj)
-            js_DestroyScript(cx, script);
-    }
-    LAST_FRAME_CHECKS(cx, scriptObj);
-    return scriptObj;
+    JS_ASSERT_IF(script, script->u.object);
+    LAST_FRAME_CHECKS(cx, script);
+    return script ? script->u.object : NULL;
 }
 
 extern JS_PUBLIC_API(JSObject *)
 JS_CompileUCScriptForPrincipalsVersion(JSContext *cx, JSObject *obj,
                                        JSPrincipals *principals,
                                        const jschar *chars, size_t length,
                                        const char *filename, uintN lineno,
                                        JSVersion version)
@@ -4637,28 +4632,24 @@ CompileFileHelper(JSContext *cx, JSObjec
 
         int c;
         while ((c = fast_getc(fp)) != EOF)
             buf[i++] = (jschar) (unsigned char) c;
     }
 
     JS_ASSERT(i <= len);
     len = i;
-    uint32 tcflags = JS_OPTIONS_TO_TCFLAGS(cx) | TCF_NEED_MUTABLE_SCRIPT;
+    uint32 tcflags = JS_OPTIONS_TO_TCFLAGS(cx) | TCF_NEED_MUTABLE_SCRIPT | TCF_NEED_SCRIPT_OBJECT;
     script = Compiler::compileScript(cx, obj, NULL, principals, tcflags, buf, len, filename, 1,
                                      cx->findVersion());
     cx->free_(buf);
     if (!script)
         return NULL;
-
-    JSObject *scriptObj = js_NewScriptObject(cx, script);
-    if (!scriptObj)
-        js_DestroyScript(cx, script);
-
-    return scriptObj;
+    JS_ASSERT(script->u.object);
+    return script->u.object;
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_CompileFile(JSContext *cx, JSObject *obj, const char *filename)
 {
     JS_THREADSAFE_ASSERT(cx->compartment != cx->runtime->atomsCompartment);
 
     CHECK_REQUEST(cx);
--- a/js/src/jsdbg.cpp
+++ b/js/src/jsdbg.cpp
@@ -40,16 +40,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "jsdbg.h"
 #include "jsapi.h"
 #include "jscntxt.h"
 #include "jsemit.h"
 #include "jsgcmark.h"
 #include "jsobj.h"
+#include "jstl.h"
 #include "jswrapper.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "jsopcodeinlines.h"
 #include "methodjit/Retcon.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
@@ -338,17 +339,17 @@ enum {
     JSSLOT_DEBUG_FRAME_PROTO,
     JSSLOT_DEBUG_OBJECT_PROTO,
     JSSLOT_DEBUG_SCRIPT_PROTO,
     JSSLOT_DEBUG_COUNT
 };
 
 Debugger::Debugger(JSObject *dbg, JSObject *hooks)
   : object(dbg), hooksObject(hooks), uncaughtExceptionHook(NULL), enabled(true),
-    hasDebuggerHandler(false), hasThrowHandler(false),
+    hasDebuggerHandler(false), hasThrowHandler(false), hasNewScriptHandler(false),
     objects(dbg->compartment()->rt), heldScripts(dbg->compartment()->rt)
 {
     // This always happens within a request on some cx.
     JSRuntime *rt = dbg->compartment()->rt;
     AutoLockGC lock(rt);
     JS_APPEND_LINK(&link, &rt->debuggerList);
     JS_INIT_CLIST(&breakpoints);
 }
@@ -506,17 +507,17 @@ Debugger::handleUncaughtException(AutoCo
     JSContext *cx = ac.context;
     if (cx->isExceptionPending()) {
         if (callHook && uncaughtExceptionHook) {
             Value fval = ObjectValue(*uncaughtExceptionHook);
             Value exc = cx->getPendingException();
             Value rv;
             cx->clearPendingException();
             if (ExternalInvoke(cx, ObjectValue(*object), fval, 1, &exc, &rv))
-                return parseResumptionValue(ac, true, rv, vp, false);
+                return vp ? parseResumptionValue(ac, true, rv, vp, false) : JSTRAP_CONTINUE;
         }
 
         if (cx->isExceptionPending()) {
             JS_ReportPendingException(cx);
             cx->clearPendingException();
         }
     }
     ac.leave();
@@ -553,17 +554,17 @@ Debugger::newCompletionValue(AutoCompart
         return false;
     }
     vp->setObject(*obj);
     return true;
 }
 
 JSTrapStatus
 Debugger::parseResumptionValue(AutoCompartment &ac, bool ok, const Value &rv, Value *vp,
-                            bool callHook)
+                               bool callHook)
 {
     vp->setUndefined();
     if (!ok)
         return handleUncaughtException(ac, vp, callHook);
     if (rv.isUndefined()) {
         ac.leave();
         return JSTRAP_CONTINUE;
     }
@@ -671,16 +672,38 @@ Debugger::handleThrow(JSContext *cx, Val
     Value rv;
     bool ok = CallMethodIfPresent(cx, hooksObject, "throw", 2, argv, &rv);
     JSTrapStatus st = parseResumptionValue(ac, ok, rv, vp);
     if (st == JSTRAP_CONTINUE)
         cx->setPendingException(exc);
     return st;
 }
 
+void
+Debugger::handleNewScript(JSContext *cx, JSScript *script, JSObject *obj, NewScriptKind kind)
+{
+    JS_ASSERT(hasNewScriptHandler);
+    AutoCompartment ac(cx, hooksObject);
+    if (!ac.enter())
+        return;
+
+    JSObject *dsobj =
+        kind == NewHeldScript ? wrapHeldScript(cx, script, obj) : wrapNonHeldScript(cx, script);
+    if (!dsobj) {
+        handleUncaughtException(ac, NULL, false);
+        return;
+    }
+
+    Value argv[1];
+    argv[0].setObject(*dsobj);
+    Value rv;
+    if (!CallMethodIfPresent(cx, hooksObject, "newScript", 1, argv, &rv))
+        handleUncaughtException(ac, NULL, true);
+}
+
 JSTrapStatus
 Debugger::dispatchHook(JSContext *cx, js::Value *vp, DebuggerObservesMethod observesEvent,
                        DebuggerHandleMethod handleEvent)
 {
     // Determine which debuggers will receive this event, and in what order.
     // Make a copy of the list, since the original is mutable and we will be
     // calling into arbitrary JS.
     // Note: In the general case, 'triggered' contains references to objects in
@@ -705,16 +728,66 @@ Debugger::dispatchHook(JSContext *cx, js
             JSTrapStatus st = (dbg->*handleEvent)(cx, vp);
             if (st != JSTRAP_CONTINUE)
                 return st;
         }
     }
     return JSTRAP_CONTINUE;
 }
 
+static bool
+AddNewScriptRecipients(GlobalObject::DebuggerVector *src, AutoValueVector *dest)
+{
+    bool wasEmpty = dest->length() == 0;
+    for (Debugger **p = src->begin(); p != src->end(); p++) {
+        Debugger *dbg = *p;
+        Value v = ObjectValue(*dbg->toJSObject());
+        if (dbg->observesNewScript() &&
+            (wasEmpty || Find(dest->begin(), dest->end(), v) == dest->end()) &&
+            !dest->append(v))
+        {
+            return false;
+        }
+    }
+    return true;
+}
+
+void
+Debugger::slowPathOnNewScript(JSContext *cx, JSScript *script, JSObject *obj, NewScriptKind kind)
+{
+    // Build the list of recipients. For compile-and-go scripts, this is the
+    // same as the generic Debugger::dispatchHook code, but non-compile-and-go
+    // scripts are not tied to particular globals. We deliver them to every
+    // debugger observing any global in the script's compartment.
+    AutoValueVector triggered(cx);
+    GlobalObject *global;
+    if (script->compileAndGo) {
+        global = obj->getGlobal();
+        if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) {
+            if (!AddNewScriptRecipients(debuggers, &triggered))
+                return;
+        }
+    } else {
+        global = NULL;
+        GlobalObjectSet &debuggees = script->compartment->getDebuggees();
+        for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
+            if (!AddNewScriptRecipients(r.front()->getDebuggers(), &triggered))
+                return;
+        }
+    }
+
+    // Deliver the event to each debugger, checking again as in
+    // Debugger::dispatchHook.
+    for (Value *p = triggered.begin(); p != triggered.end(); p++) {
+        Debugger *dbg = Debugger::fromJSObject(&p->toObject());
+        if ((!global || dbg->debuggees.has(global)) && dbg->hasNewScriptHandler)
+            dbg->handleNewScript(cx, script, obj, kind);
+    }
+}
+
 JSTrapStatus
 Debugger::onTrap(JSContext *cx, Value *vp)
 {
     StackFrame *fp = cx->fp();
     GlobalObject *scriptGlobal = fp->scopeChain().getGlobal();
     jsbytecode *pc = cx->regs().pc;
     BreakpointSite *site = cx->compartment->getBreakpointSite(pc);
     JSOp op = site->realOpcode;
@@ -801,31 +874,31 @@ Debugger::markKeysInCompartment(JSTracer
 //
 // We must scan all Debugger objects regardless of whether they *currently*
 // have any debuggees in the compartment being GC'd, because the WeakMap
 // entries persist even when debuggees are removed.
 //
 // This happens during the initial mark phase, not iterative marking, because
 // all the edges being reported here are strong references.
 //
-void 
+void
 Debugger::markCrossCompartmentDebuggerObjectReferents(JSTracer *tracer)
 {
     JSRuntime *rt = tracer->context->runtime;
     JSCompartment *comp = rt->gcCurrentCompartment;
 
     // Mark all objects in comp that are referents of Debugger.Objects in other
     // compartments.
     for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) {
         Debugger *dbg = Debugger::fromLinks(p);
         if (dbg->object->compartment() != comp) {
             markKeysInCompartment(tracer, dbg->objects);
             markKeysInCompartment(tracer, dbg->heldScripts);
         }
-    }         
+    }
 }
 
 // This method has two tasks:
 //   1. Mark Debugger objects that are unreachable except for debugger hooks that
 //      may yet be called.
 //   2. Mark breakpoint handlers.
 //
 // This happens during the incremental long tail of the GC mark phase. This
@@ -1004,28 +1077,32 @@ JSBool
 Debugger::setHooks(JSContext *cx, uintN argc, Value *vp)
 {
     REQUIRE_ARGC("Debugger.set hooks", 1);
     THISOBJ(cx, vp, Debugger, "set hooks", thisobj, dbg);
     if (!vp[2].isObject())
         return ReportObjectRequired(cx);
     JSObject *hooksobj = &vp[2].toObject();
 
-    bool hasDebuggerHandler, hasThrow;
+    bool hasDebuggerHandler, hasThrow, hasNewScript;
     JSBool found;
     if (!JS_HasProperty(cx, hooksobj, "debuggerHandler", &found))
         return false;
     hasDebuggerHandler = !!found;
     if (!JS_HasProperty(cx, hooksobj, "throw", &found))
         return false;
     hasThrow = !!found;
+    if (!JS_HasProperty(cx, hooksobj, "newScript", &found))
+        return false;
+    hasNewScript = !!found;
 
     dbg->hooksObject = hooksobj;
     dbg->hasDebuggerHandler = hasDebuggerHandler;
     dbg->hasThrowHandler = hasThrow;
+    dbg->hasNewScriptHandler = hasNewScript;
     vp->setUndefined();
     return true;
 }
 
 JSBool
 Debugger::getEnabled(JSContext *cx, uintN argc, Value *vp)
 {
     THISOBJ(cx, vp, Debugger, "get enabled", thisobj, dbg);
@@ -1473,32 +1550,36 @@ Class DebuggerScript_class = {
     NULL,                 /* xdrObject   */
     NULL,                 /* hasInstance */
     DebuggerScript_trace
 };
 
 JSObject *
 Debugger::newDebuggerScript(JSContext *cx, JSScript *script, JSObject *holder)
 {
+    assertSameCompartment(cx, object);
+
     JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_SCRIPT_PROTO).toObject();
     JS_ASSERT(proto);
     JSObject *scriptobj = NewNonFunction<WithProto::Given>(cx, &DebuggerScript_class, proto, NULL);
     if (!scriptobj || !scriptobj->ensureClassReservedSlots(cx))
         return false;
     scriptobj->setPrivate(script);
     scriptobj->setReservedSlot(JSSLOT_DEBUGSCRIPT_OWNER, ObjectValue(*object));
     scriptobj->setReservedSlot(JSSLOT_DEBUGSCRIPT_HOLDER, PrivateValue(holder));
 
     return scriptobj;
 }
 
 JSObject *
 Debugger::wrapHeldScript(JSContext *cx, JSScript *script, JSObject *obj)
 {
     assertSameCompartment(cx, object);
+    JS_ASSERT(cx->compartment != script->compartment);
+    JS_ASSERT(script->compartment == obj->compartment());
 
     ScriptWeakMap::AddPtr p = heldScripts.lookupForAdd(obj);
     if (!p) {
         JSObject *scriptobj = newDebuggerScript(cx, script, obj);
         // The allocation may have caused a GC, which can remove table entries.
         if (!scriptobj || !heldScripts.relookupOrAdd(p, obj, scriptobj))
             return NULL;
     }
@@ -1518,20 +1599,23 @@ Debugger::wrapJSAPIScript(JSContext *cx,
 {
     JS_ASSERT(obj->isScript());
     return wrapHeldScript(cx, obj->getScript(), obj);
 }
 
 JSObject *
 Debugger::wrapNonHeldScript(JSContext *cx, JSScript *script)
 {
+    assertSameCompartment(cx, object);
     JS_ASSERT(cx->compartment != script->compartment);
+
     ScriptMap::AddPtr p = nonHeldScripts.lookupForAdd(script);
     if (!p) {
         JSObject *scriptobj = newDebuggerScript(cx, script, NULL);
+
         // The allocation may have caused a GC, which can remove table entries.
         if (!scriptobj || !nonHeldScripts.relookupOrAdd(p, script, scriptobj))
             return NULL;
     }
 
     JS_ASSERT(GetScriptReferent(p->value) == script);
     return p->value;
 }
--- a/js/src/jsdbg.h
+++ b/js/src/jsdbg.h
@@ -53,28 +53,32 @@
 #include "vm/GlobalObject.h"
 
 namespace js {
 
 class Debugger {
     friend class js::Breakpoint;
     friend JSBool (::JS_DefineDebuggerObject)(JSContext *cx, JSObject *obj);
 
+  public:
+    enum NewScriptKind { NewNonHeldScript, NewHeldScript };
+
   private:
     JSCList link;                       // See JSRuntime::debuggerList.
     JSObject *object;                   // The Debugger object. Strong reference.
     GlobalObjectSet debuggees;          // Debuggee globals. Cross-compartment weak references.
     JSObject *hooksObject;              // See Debugger.prototype.hooks. Strong reference.
     JSObject *uncaughtExceptionHook;    // Strong reference.
     bool enabled;
 
     // True if hooksObject had a property of the respective name when the hooks
     // property was set.
     bool hasDebuggerHandler;            // hooks.debuggerHandler
     bool hasThrowHandler;               // hooks.throw
+    bool hasNewScriptHandler;           // hooks.newScript
 
     JSCList breakpoints;                // cyclic list of all js::Breakpoints in this debugger
 
     // Weak references to stack frames that are currently on the stack and thus
     // necessarily alive. We drop them as soon as they leave the stack (see
     // slowPathLeaveStackFrame) and in removeDebuggee.
     typedef HashMap<StackFrame *, JSObject *, DefaultHasher<StackFrame *>, SystemAllocPolicy>
         FrameMap;
@@ -108,16 +112,29 @@ class Debugger {
     // "dead" when the eval call returns.
     ScriptMap nonHeldScripts;
 
     bool addDebuggeeGlobal(JSContext *cx, GlobalObject *obj);
     void removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
                               GlobalObjectSet::Enum *compartmentEnum,
                               GlobalObjectSet::Enum *debugEnum);
 
+    // Cope with an error or exception in a debugger hook.
+    //
+    // If callHook is true, then call the uncaughtExceptionHook, if any. If
+    // additionally vp is non-null, then parse the value returned by
+    // uncaughtExceptionHook as a resumption value.
+    //
+    // If there is no uncaughtExceptionHook, or if it fails, report and clear
+    // the pending exception on ac.context and return JSTRAP_ERROR.
+    //
+    // This always calls ac.leave(); ac is a parameter because this method must
+    // do some things in the debugger compartment and some things in the
+    // debuggee compartment.
+    //
     JSTrapStatus handleUncaughtException(AutoCompartment &ac, Value *vp, bool callHook);
     JSTrapStatus parseResumptionValue(AutoCompartment &ac, bool ok, const Value &rv, Value *vp,
                                       bool callHook = true);
     JSObject *unwrapDebuggeeArgument(JSContext *cx, Value *vp);
 
     static void traceObject(JSTracer *trc, JSObject *obj);
     void trace(JSTracer *trc);
     static void finalize(JSContext *cx, JSObject *obj);
@@ -138,16 +155,18 @@ class Debugger {
     static JSBool clearAllBreakpoints(JSContext *cx, uintN argc, Value *vp);
     static JSBool construct(JSContext *cx, uintN argc, Value *vp);
     static JSPropertySpec properties[];
     static JSFunctionSpec methods[];
 
     inline bool hasAnyLiveHooks() const;
 
     static void slowPathLeaveStackFrame(JSContext *cx);
+    static void slowPathOnNewScript(JSContext *cx, JSScript *script, JSObject *obj,
+                                    NewScriptKind kind);
     static void slowPathOnDestroyScript(JSScript *script);
 
     typedef bool (Debugger::*DebuggerObservesMethod)() const;
     typedef JSTrapStatus (Debugger::*DebuggerHandleMethod)(JSContext *, Value *) const;
     static JSTrapStatus dispatchHook(JSContext *cx, js::Value *vp,
                                      DebuggerObservesMethod observesEvent,
                                      DebuggerHandleMethod handleEvent);
 
@@ -160,16 +179,22 @@ class Debugger {
     // Allocate and initialize a Debugger.Script instance whose referent is |script| and
     // whose holder is |obj|. If |obj| is NULL, this creates a Debugger.Script whose holder
     // is null, for non-held scripts.
     JSObject *newDebuggerScript(JSContext *cx, JSScript *script, JSObject *obj);
 
     // Helper function for wrapFunctionScript and wrapJSAPIscript.
     JSObject *wrapHeldScript(JSContext *cx, JSScript *script, JSObject *obj);
 
+    // Receive a "new script" event from the engine. A new script was compiled
+    // or deserialized. If kind is NewHeldScript, obj is the holder
+    // object. Otherwise kind is NewNonHeldScript and obj is an arbitrary
+    // object in the same global as the non-held function being called???wtf.
+    void handleNewScript(JSContext *cx, JSScript *script, JSObject *obj, NewScriptKind kind);
+
     // Remove script from our table of non-held scripts.
     void destroyNonHeldScript(JSScript *script);
 
     static inline Debugger *fromLinks(JSCList *links);
     inline Breakpoint *firstBreakpoint() const;
 
   public:
     Debugger(JSObject *dbg, JSObject *hooks);
@@ -200,21 +225,24 @@ class Debugger {
     static bool mark(GCMarker *trc, JSGCInvocationKind gckind);
     static void sweepAll(JSContext *cx);
     static void detachAllDebuggersFromGlobal(JSContext *cx, GlobalObject *global,
                                              GlobalObjectSet::Enum *compartmentEnum);
 
     static inline void leaveStackFrame(JSContext *cx);
     static inline JSTrapStatus onDebuggerStatement(JSContext *cx, js::Value *vp);
     static inline JSTrapStatus onThrow(JSContext *cx, js::Value *vp);
+    static inline void onNewScript(JSContext *cx, JSScript *script, JSObject *obj,
+                                   NewScriptKind kind);
     static inline void onDestroyScript(JSScript *script);
     static JSTrapStatus onTrap(JSContext *cx, Value *vp);
 
     /**************************************** Functions for use by jsdbg.cpp. */
 
+    inline bool observesNewScript() const;
     inline bool observesScope(JSObject *obj) const;
     inline bool observesFrame(StackFrame *fp) const;
 
     // Precondition: *vp is a value from a debuggee compartment and cx is in
     // the debugger's compartment.
     //
     // Wrap *vp for the debugger compartment, wrap it in a Debugger.Object if it's
     // an object, store the result in *vp, and return true.
@@ -269,22 +297,16 @@ class Debugger {
     JSObject *wrapNonHeldScript(JSContext *cx, JSScript *script);
 
   private:
     // Prohibit copying.
     Debugger(const Debugger &);
     Debugger & operator=(const Debugger &);
 };
 
-bool
-Debugger::hasAnyLiveHooks() const
-{
-    return enabled && (hasDebuggerHandler || hasThrowHandler || !JS_CLIST_IS_EMPTY(&breakpoints));
-}
-
 class BreakpointSite {
     friend class js::Breakpoint;
     friend class ::JSCompartment;
     friend class js::Debugger;
 
   public:
     JSScript * const script;
     jsbytecode * const pc;
@@ -334,29 +356,26 @@ class Breakpoint {
     Breakpoint(Debugger *debugger, BreakpointSite *site, JSObject *handler);
     void destroy(JSContext *cx, BreakpointSiteMap::Enum *e = NULL);
     Breakpoint *nextInDebugger();
     Breakpoint *nextInSite();
     JSObject *getHandler() const { return handler; }
 };
 
 bool
-Debugger::observesScope(JSObject *obj) const
+Debugger::hasAnyLiveHooks() const
 {
-    return debuggees.has(obj->getGlobal());
+    return enabled && (hasDebuggerHandler ||
+                       hasThrowHandler ||
+                       hasNewScriptHandler ||
+                       !JS_CLIST_IS_EMPTY(&breakpoints));
 }
 
-bool
-Debugger::observesFrame(StackFrame *fp) const
-{
-    return observesScope(&fp->scopeChain());
-}
-
-js::Debugger *
-js::Debugger::fromLinks(JSCList *links)
+Debugger *
+Debugger::fromLinks(JSCList *links)
 {
     unsigned char *p = reinterpret_cast<unsigned char *>(links);
     return reinterpret_cast<Debugger *>(p - offsetof(Debugger, link));
 }
 
 
 Breakpoint *
 Debugger::firstBreakpoint() const
@@ -375,16 +394,34 @@ Debugger::toJSObject() const
 
 Debugger *
 Debugger::fromJSObject(JSObject *obj)
 {
     JS_ASSERT(obj->getClass() == &jsclass);
     return (Debugger *) obj->getPrivate();
 }
 
+bool
+Debugger::observesNewScript() const
+{
+    return enabled && hasNewScriptHandler;
+}
+
+bool
+Debugger::observesScope(JSObject *obj) const
+{
+    return debuggees.has(obj->getGlobal());
+}
+
+bool
+Debugger::observesFrame(StackFrame *fp) const
+{
+    return observesScope(&fp->scopeChain());
+}
+
 void
 Debugger::leaveStackFrame(JSContext *cx)
 {
     if (!cx->compartment->getDebuggees().empty() || !cx->compartment->breakpointSites.empty())
         slowPathLeaveStackFrame(cx);
 }
 
 JSTrapStatus
@@ -403,16 +440,24 @@ Debugger::onThrow(JSContext *cx, js::Val
     return cx->compartment->getDebuggees().empty()
            ? JSTRAP_CONTINUE
            : dispatchHook(cx, vp,
                           DebuggerObservesMethod(&Debugger::observesThrow),
                           DebuggerHandleMethod(&Debugger::handleThrow));
 }
 
 void
+Debugger::onNewScript(JSContext *cx, JSScript *script, JSObject *obj, NewScriptKind kind)
+{
+    JS_ASSERT_IF(kind == NewNonHeldScript && script->compileAndGo, obj);
+    if (!script->compartment->getDebuggees().empty())
+        slowPathOnNewScript(cx, script, obj, kind);
+}
+
+void
 Debugger::onDestroyScript(JSScript *script)
 {
     if (!script->compartment->getDebuggees().empty())
         slowPathOnDestroyScript(script);
 }
 
 extern JSBool
 EvaluateInScope(JSContext *cx, JSObject *scobj, StackFrame *fp, const jschar *chars,
--- a/js/src/jsemit.h
+++ b/js/src/jsemit.h
@@ -268,16 +268,21 @@ struct JSStmtInfo {
  * function statement (as opposed to a function definition).
  *
  * This flag is *not* inherited by enclosed or enclosing functions; it
  * applies only to the function in whose flags it appears.
  */
 #define TCF_FUN_EXTENSIBLE_SCOPE 0x20000000
 
 /*
+ * The caller is JS_Compile*Script*.
+ */
+#define TCF_NEED_SCRIPT_OBJECT 0x40000000
+
+/*
  * Flags to check for return; vs. return expr; in a function.
  */
 #define TCF_RETURN_FLAGS        (TCF_RETURN_EXPR | TCF_RETURN_VOID)
 
 /*
  * Sticky deoptimization flags to propagate from FunctionBody.
  */
 #define TCF_FUN_FLAGS           (TCF_FUN_SETS_OUTER_NAME |                    \
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -47,16 +47,17 @@
 #include "jsbit.h"
 #include "jsutil.h"
 #include "jsapi.h"
 #include "jsarray.h"
 #include "jsatom.h"
 #include "jsbool.h"
 #include "jsbuiltins.h"
 #include "jscntxt.h"
+#include "jsdbg.h"
 #include "jsversion.h"
 #include "jsemit.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jsgcmark.h"
 #include "jsinterp.h"
 #include "jslock.h"
 #include "jsnum.h"
@@ -2719,16 +2720,17 @@ js_CloneFunctionObject(JSContext *cx, JS
 
             cfun->u.i.script = js_CloneScript(cx, script);
             if (!cfun->script())
                 return NULL;
 #ifdef CHECK_SCRIPT_OWNER
             cfun->script()->owner = NULL;
 #endif
             js_CallNewScriptHook(cx, cfun->script(), cfun);
+            Debugger::onNewScript(cx, cfun->script(), cfun, Debugger::NewHeldScript);
         }
     }
     return clone;
 }
 
 #ifdef JS_TRACER
 JS_DEFINE_CALLINFO_4(extern, OBJECT, js_CloneFunctionObject, CONTEXT, FUNCTION, OBJECT, OBJECT, 0,
                      nanojit::ACCSET_STORE_ANY)
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -901,17 +901,17 @@ Compiler::compileScript(JSContext *cx, J
 {
     JSArenaPool codePool, notePool;
     TokenKind tt;
     JSParseNode *pn;
     JSScript *script;
     bool inDirectivePrologue;
 
     JS_ASSERT(!(tcflags & ~(TCF_COMPILE_N_GO | TCF_NO_SCRIPT_RVAL | TCF_NEED_MUTABLE_SCRIPT |
-                            TCF_COMPILE_FOR_EVAL)));
+                            TCF_COMPILE_FOR_EVAL | TCF_NEED_SCRIPT_OBJECT)));
 
     /*
      * The scripted callerFrame can only be given for compile-and-go scripts
      * and non-zero static level requires callerFrame.
      */
     JS_ASSERT_IF(callerFrame, tcflags & TCF_COMPILE_N_GO);
     JS_ASSERT_IF(staticLevel != 0, callerFrame);
 
@@ -1125,20 +1125,19 @@ Compiler::compileScript(JSContext *cx, J
     JS_FinishArenaPool(&notePool);
     return script;
 
   too_many_slots:
     parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_TOO_MANY_LOCALS);
     /* Fall through. */
 
   late_error:
-    if (script) {
+    if (script && !script->u.object)
         js_DestroyScript(cx, script);
-        script = NULL;
-    }
+    script = NULL;
     goto out;
 }
 
 bool
 Compiler::defineGlobals(JSContext *cx, GlobalScope &globalScope, JSScript *script)
 {
     if (!globalScope.defs.length())
         return true;
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1318,46 +1318,61 @@ JSScript::NewScriptFromCG(JSContext *cx,
         memcpy(script->closedSlots, &cg->closedArgs[0], script->nClosedArgs * sizeof(uint32));
     if (script->nClosedVars) {
         memcpy(&script->closedSlots[script->nClosedArgs], &cg->closedVars[0],
                script->nClosedVars * sizeof(uint32));
     }
 
     script->bindings.transfer(cx, &cg->bindings);
 
-    /*
-     * We initialize fun->u.script to be the script constructed above
-     * so that the debugger has a valid FUN_SCRIPT(fun).
-     */
     fun = NULL;
     if (cg->inFunction()) {
+        /*
+         * We initialize fun->u.i.script to be the script constructed above
+         * so that the debugger has a valid FUN_SCRIPT(fun).
+         */
         fun = cg->fun();
         JS_ASSERT(fun->isInterpreted());
         JS_ASSERT(!fun->script());
 #ifdef DEBUG
         if (JSScript::isValidOffset(script->upvarsOffset))
             JS_ASSERT(script->upvars()->length == script->bindings.countUpvars());
         else
             JS_ASSERT(script->bindings.countUpvars() == 0);
 #endif
         fun->u.i.script = script;
 #ifdef CHECK_SCRIPT_OWNER
         script->owner = NULL;
 #endif
         if (cg->flags & TCF_FUN_HEAVYWEIGHT)
             fun->flags |= JSFUN_HEAVYWEIGHT;
+    } else {
+        /*
+         * Initialize script->object, if necessary, so that the debugger has a
+         * valid holder object.
+         */
+        if ((cg->flags & TCF_NEED_SCRIPT_OBJECT) && !js_NewScriptObject(cx, script))
+            goto bad;
     }
 
     /* Tell the debugger about this compiled script. */
     js_CallNewScriptHook(cx, script, fun);
+    if (!cg->parent) {
+        Debugger::onNewScript(cx, script,
+                              fun ? fun : script->u.object ? script->u.object : cg->scopeChain(),
+                              (fun || script->u.object)
+                              ? Debugger::NewHeldScript
+                              : Debugger::NewNonHeldScript);
+    }
 
     return script;
 
 bad:
-    js_DestroyScript(cx, script);
+    if (!script->u.object)
+        js_DestroyScript(cx, script);
     return NULL;
 }
 
 size_t
 JSScript::totalSize()
 {
     return code +
            length * sizeof(jsbytecode) +
--- a/js/src/jsxdrapi.cpp
+++ b/js/src/jsxdrapi.cpp
@@ -44,16 +44,17 @@
 #include <string.h>
 #include "jstypes.h"
 #include "jsstdint.h"
 #include "jsutil.h"
 #include "jsdhash.h"
 #include "jsprf.h"
 #include "jsapi.h"
 #include "jscntxt.h"
+#include "jsdbg.h"
 #include "jsnum.h"
 #include "jsobj.h"              /* js_XDRObject */
 #include "jsscript.h"           /* js_XDRScript */
 #include "jsstr.h"
 #include "jsxdrapi.h"
 
 #include "jsobjinlines.h"
 
@@ -712,22 +713,23 @@ JS_XDRScriptObject(JSXDRState *xdr, JSOb
         state.filename = script->filename;
     if (!JS_XDRCStringOrNull(xdr, (char **) &state.filename))
         return false;
 
     if (!js_XDRScript(xdr, &script))
         return false;
 
     if (xdr->mode == JSXDR_DECODE) {
-        js_CallNewScriptHook(xdr->cx, script, NULL);
         *scriptObjp = js_NewScriptObject(xdr->cx, script);
         if (!*scriptObjp) {
             js_DestroyScript(xdr->cx, script);
             return false;
         }
+        js_CallNewScriptHook(xdr->cx, script, NULL);
+        Debugger::onNewScript(xdr->cx, script, *scriptObjp, Debugger::NewHeldScript);
     }
 
     return true;
 }
 
 #define CLASS_REGISTRY_MIN      8
 #define CLASS_INDEX_TO_ID(i)    ((i)+1)
 #define CLASS_ID_TO_INDEX(id)   ((id)-1)
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2980,34 +2980,55 @@ Intern(JSContext *cx, uintN argc, jsval 
     return true;
 }
 
 static JSBool
 Clone(JSContext *cx, uintN argc, jsval *vp)
 {
     JSObject *funobj, *parent, *clone;
 
-    if (!argc)
+    if (!argc) {
+        JS_ReportError(cx, "Invalid arguments to clone");
         return JS_FALSE;
+    }
 
     jsval *argv = JS_ARGV(cx, vp);
-    if (VALUE_IS_FUNCTION(cx, argv[0])) {
-        funobj = JSVAL_TO_OBJECT(argv[0]);
-    } else {
-        JSFunction *fun = JS_ValueToFunction(cx, argv[0]);
-        if (!fun)
+    {
+        JSAutoEnterCompartment ac;
+        if (JSVAL_IS_OBJECT(argv[0]) && JSVAL_TO_OBJECT(argv[0])->isCrossCompartmentWrapper()) {
+            JSObject *obj = JSVAL_TO_OBJECT(argv[0])->unwrap();
+            if (!ac.enter(cx, obj))
+                return JS_FALSE;
+            argv[0] = OBJECT_TO_JSVAL(obj);
+        }
+        if (VALUE_IS_FUNCTION(cx, argv[0])) {
+            funobj = JSVAL_TO_OBJECT(argv[0]);
+        } else {
+            JSFunction *fun = JS_ValueToFunction(cx, argv[0]);
+            if (!fun)
+                return JS_FALSE;
+            funobj = JS_GetFunctionObject(fun);
+        }
+    }
+    if (funobj->compartment() != cx->compartment) {
+        JSFunction *fun = funobj->getFunctionPrivate();
+        if (fun->isInterpreted() && fun->u.i.script->compileAndGo) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE,
+                                 "function", "compile-and-go");
             return JS_FALSE;
-        funobj = JS_GetFunctionObject(fun);
-    }
+        }
+    }
+
     if (argc > 1) {
         if (!JS_ValueToObject(cx, argv[1], &parent))
             return JS_FALSE;
     } else {
-        parent = JS_GetParent(cx, funobj);
-    }
+        parent = JS_GetParent(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)));
+    }
+
     clone = JS_CloneFunctionObject(cx, funobj, parent);
     if (!clone)
         return JS_FALSE;
     *vp = OBJECT_TO_JSVAL(clone);
     return JS_TRUE;
 }
 
 static JSBool