Bug 667915 - Don't let content JS consume all the stack and cause chrome JS to OOM (r=waldo,mrbkap)
authorLuke Wagner <luke@mozilla.com>
Thu, 30 Jun 2011 09:26:56 -0700
changeset 73249 28be8df0deb7de5209b976009ae8519eac374272
parent 73248 f317927aafcb8d7bbc679b2aad35d631e19f927a
child 73250 0661e7077dceb6cbaf3c50b2b624cddcda04765f
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)
reviewerswaldo, mrbkap
bugs667915
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 667915 - Don't let content JS consume all the stack and cause chrome JS to OOM (r=waldo,mrbkap)
caps/include/nsSystemPrincipal.h
caps/src/nsScriptSecurityManager.cpp
caps/src/nsSystemPrincipal.cpp
js/src/jit-test/tests/basic/testOOMInAutoEnterCompartment.js
js/src/jit-test/tests/basic/testOverOOMInFixupArity.js
js/src/jsapi-tests/Makefile.in
js/src/jsapi-tests/testChromeBuffer.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscntxt.h
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsinterp.cpp
js/src/jsparse.cpp
js/src/jsscope.cpp
js/src/jsstr.cpp
js/src/jstracer.cpp
js/src/jsutil.h
js/src/jswrapper.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/MethodJIT.cpp
js/src/methodjit/MonoIC.cpp
js/src/methodjit/StubCalls.cpp
js/src/shell/js.cpp
js/src/tests/js1_5/Array/jstests.list
js/src/tests/js1_5/Array/regress-350256-01.js
js/src/tests/js1_5/Array/regress-350256-02.js
js/src/tests/js1_5/Array/regress-350256-03.js
js/src/vm/ArgumentsObject-inl.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/caps/include/nsSystemPrincipal.h
+++ b/caps/include/nsSystemPrincipal.h
@@ -54,17 +54,17 @@ class nsSystemPrincipal : public nsIPrin
 {
 public:
     // Our refcount is managed by mJSPrincipals.  Use this macro to avoid
     // an extra refcount member.
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIPRINCIPAL
     NS_DECL_NSISERIALIZABLE
 
-    nsresult Init();
+    nsresult Init(JSPrincipals **jsprin);
 
     nsSystemPrincipal();
 
 protected:
     virtual ~nsSystemPrincipal(void);
 
     nsJSPrincipals mJSPrincipals;
     // XXX Probably unnecessary.  See bug 143559.
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -3374,17 +3374,18 @@ nsresult nsScriptSecurityManager::Init()
 
     rv = bundleService->CreateBundle("chrome://global/locale/security/caps.properties", &sStrBundle);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Create our system principal singleton
     nsRefPtr<nsSystemPrincipal> system = new nsSystemPrincipal();
     NS_ENSURE_TRUE(system, NS_ERROR_OUT_OF_MEMORY);
 
-    rv = system->Init();
+    JSPrincipals *jsprin;
+    rv = system->Init(&jsprin);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mSystemPrincipal = system;
 
     //-- Register security check callback in the JS engine
     //   Currently this is used to control access to function.caller
     nsCOMPtr<nsIJSRuntimeService> runtimeService =
         do_QueryInterface(sXPConnect, &rv);
@@ -3401,16 +3402,18 @@ nsresult nsScriptSecurityManager::Init()
     };
 
 #ifdef DEBUG
     JSSecurityCallbacks *oldcallbacks =
 #endif
     JS_SetRuntimeSecurityCallbacks(sRuntime, &securityCallbacks);
     NS_ASSERTION(!oldcallbacks, "Someone else set security callbacks!");
 
+    JS_SetTrustedPrincipals(sRuntime, jsprin);
+
     return NS_OK;
 }
 
 static nsScriptSecurityManager *gScriptSecMan = nsnull;
 
 jsid nsScriptSecurityManager::sEnabledID   = JSID_VOID;
 
 nsScriptSecurityManager::~nsScriptSecurityManager(void)
@@ -3424,16 +3427,17 @@ nsScriptSecurityManager::~nsScriptSecuri
     gScriptSecMan = nsnull;
 }
 
 void
 nsScriptSecurityManager::Shutdown()
 {
     if (sRuntime) {
         JS_SetRuntimeSecurityCallbacks(sRuntime, NULL);
+        JS_SetTrustedPrincipals(sRuntime, NULL);
         sRuntime = nsnull;
     }
     sEnabledID = JSID_VOID;
 
     NS_IF_RELEASE(sIOService);
     NS_IF_RELEASE(sXPConnect);
     NS_IF_RELEASE(sJSContextStack);
     NS_IF_RELEASE(sStrBundle);
--- a/caps/src/nsSystemPrincipal.cpp
+++ b/caps/src/nsSystemPrincipal.cpp
@@ -309,24 +309,28 @@ nsSystemPrincipal::nsSystemPrincipal()
 }
 
 // Don't rename the system principal!
 // The JS engine (NewCompartment) relies on this name. 
 // XXX: bug 669123 will fix this hack.
 #define SYSTEM_PRINCIPAL_SPEC "[System Principal]"
 
 nsresult
-nsSystemPrincipal::Init()
+nsSystemPrincipal::Init(JSPrincipals **jsprin)
 {
     // Use an nsCString so we only do the allocation once here and then
     // share with nsJSPrincipals
     nsCString str(SYSTEM_PRINCIPAL_SPEC);
     if (!str.EqualsLiteral(SYSTEM_PRINCIPAL_SPEC)) {
         NS_WARNING("Out of memory initializing system principal");
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    return mJSPrincipals.Init(this, str);
+    nsresult rv = mJSPrincipals.Init(this, str);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    *jsprin = &mJSPrincipals;
+    return NS_OK;
 }
 
 nsSystemPrincipal::~nsSystemPrincipal(void)
 {
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/testOOMInAutoEnterCompartment.js
@@ -0,0 +1,12 @@
+foo = evalcx("(function foo() { foo.bar() })");
+foo.bar = evalcx("(function bar() {})");
+
+function fatty() {
+    try {
+        fatty();
+    } catch (e) {
+        foo();
+    }
+}
+
+fatty();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/testOverOOMInFixupArity.js
@@ -0,0 +1,15 @@
+function foo() {
+    bar(1,2,3,4,5,6,7,8,9);
+}
+
+function bar() {
+    foo(1,2,3,4,5,6,7,8,9);
+}
+
+var caught = false;
+try {
+    foo();
+} catch (e) {
+    caught = true;
+}
+assertEq(caught, true);
--- a/js/src/jsapi-tests/Makefile.in
+++ b/js/src/jsapi-tests/Makefile.in
@@ -81,16 +81,17 @@ CPPSRCS = \
   testThreadGC.cpp \
   testThreads.cpp \
   testTrap.cpp \
   testUTF8.cpp \
   testVersion.cpp \
   testXDR.cpp \
   testCustomIterator.cpp \
   testExternalStrings.cpp \
+  testChromeBuffer.cpp \
   $(NULL)
 
 # Disabled: an entirely unrelated test seems to cause this to fail.  Moreover,
 # given the test's dependence on interactions between the compiler, the GC, and
 # conservative stack scanning, the fix isn't obvious: more investigation
 # needed.
 #CPPSRCS += \
 #  testRegExpInstanceProperties.cpp \
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testChromeBuffer.cpp
@@ -0,0 +1,161 @@
+#include "tests.h"
+
+JSPrincipals system_principals = {
+    NULL, NULL, NULL, 1, NULL, NULL
+};
+
+JSClass global_class = {
+    "global",
+    JSCLASS_IS_GLOBAL | JSCLASS_GLOBAL_FLAGS,
+    JS_PropertyStub,
+    JS_PropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub,
+    JS_FinalizeStub,
+    JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+JS::Anchor<JSObject *> trusted_glob, trusted_fun;
+
+JSBool
+CallTrusted(JSContext *cx, uintN argc, jsval *vp)
+{
+    if (!JS_SaveFrameChain(cx))
+        return JS_FALSE;
+
+    JSBool ok = JS_FALSE;
+    {
+        JSAutoEnterCompartment ac;
+        ok = ac.enter(cx, trusted_glob.get());
+        if (!ok)
+            goto out;
+        ok = JS_CallFunctionValue(cx, NULL, OBJECT_TO_JSVAL(trusted_fun.get()),
+                                  0, NULL, vp);
+    }
+  out:
+    JS_RestoreFrameChain(cx);
+    return ok;
+}
+
+BEGIN_TEST(testChromeBuffer)
+{
+    JS_SetTrustedPrincipals(rt, &system_principals);
+
+    JSFunction *fun;
+    JSObject *o;
+
+    CHECK(o = JS_NewCompartmentAndGlobalObject(cx, &global_class, &system_principals));
+    trusted_glob.set(o);
+
+    /*
+     * Check that, even after untrusted content has exhausted the stack, code
+     * compiled with "trusted principals" can run using reserved trusted-only
+     * buffer space.
+     */
+    {
+        {
+            JSAutoEnterCompartment ac;
+            CHECK(ac.enter(cx, trusted_glob.get()));
+            const char *paramName = "x";
+            const char *bytes = "return x ? 1 + trusted(x-1) : 0";
+            CHECK(fun = JS_CompileFunctionForPrincipals(cx, trusted_glob.get(), &system_principals,
+                                                        "trusted", 1, &paramName, bytes, strlen(bytes),
+                                                        "", 0));
+            trusted_fun.set(JS_GetFunctionObject(fun));
+        }
+
+        jsval v = OBJECT_TO_JSVAL(trusted_fun.get());
+        CHECK(JS_WrapValue(cx, &v));
+
+        const char *paramName = "trusted";
+        const char *bytes = "try {                                      "
+                            "  return untrusted(trusted);               "
+                            "} catch (e) {                              "
+                            "  return trusted(100);                     "
+                            "}                                          ";
+        CHECK(fun = JS_CompileFunction(cx, global, "untrusted", 1, &paramName,
+                                       bytes, strlen(bytes), "", 0));
+
+        jsval rval;
+        CHECK(JS_CallFunction(cx, NULL, fun, 1, &v, &rval));
+        CHECK(JSVAL_TO_INT(rval) == 100);
+    }
+
+    /*
+     * Check that content called from chrome in the reserved-buffer space
+     * immediately ooms.
+     */
+    {
+        {
+            JSAutoEnterCompartment ac;
+            CHECK(ac.enter(cx, trusted_glob.get()));
+            const char *paramName = "untrusted";
+            const char *bytes = "try {                                  "
+                                "  untrusted();                         "
+                                "} catch (e) {                          "
+                                "  return 'From trusted: ' + e;         "
+                                "}                                      ";
+            CHECK(fun = JS_CompileFunctionForPrincipals(cx, trusted_glob.get(), &system_principals,
+                                                        "trusted", 1, &paramName, bytes, strlen(bytes),
+                                                        "", 0));
+            trusted_fun.set(JS_GetFunctionObject(fun));
+        }
+
+        jsval v = OBJECT_TO_JSVAL(trusted_fun.get());
+        CHECK(JS_WrapValue(cx, &v));
+
+        const char *paramName = "trusted";
+        const char *bytes = "try {                                      "
+                            "  return untrusted(trusted);               "
+                            "} catch (e) {                              "
+                            "  return trusted(untrusted);               "
+                            "}                                          ";
+        CHECK(fun = JS_CompileFunction(cx, global, "untrusted", 1, &paramName,
+                                       bytes, strlen(bytes), "", 0));
+
+        jsval rval;
+        CHECK(JS_CallFunction(cx, NULL, fun, 1, &v, &rval));
+        JSBool match;
+        CHECK(JS_StringEqualsAscii(cx, JSVAL_TO_STRING(rval), "From trusted: InternalError: too much recursion", &match));
+        CHECK(match);
+    }
+
+    /*
+     * Check that JS_SaveFrameChain called on the way from content to chrome
+     * (say, as done by XPCJSContextSTack::Push) works.
+     */
+    {
+        {
+            JSAutoEnterCompartment ac;
+            CHECK(ac.enter(cx, trusted_glob.get()));
+            const char *bytes = "return 42";
+            CHECK(fun = JS_CompileFunctionForPrincipals(cx, trusted_glob.get(), &system_principals,
+                                                        "trusted", 0, NULL, bytes, strlen(bytes),
+                                                        "", 0));
+            trusted_fun.set(JS_GetFunctionObject(fun));
+        }
+
+        JSFunction *fun = JS_NewFunction(cx, CallTrusted, 0, 0, global, "callTrusted");
+        JS::Anchor<JSObject *> callTrusted(JS_GetFunctionObject(fun));
+
+        const char *paramName = "f";
+        const char *bytes = "try {                                      "
+                            "  return untrusted(trusted);               "
+                            "} catch (e) {                              "
+                            "  return f();                              "
+                            "}                                          ";
+        CHECK(fun = JS_CompileFunction(cx, global, "untrusted", 1, &paramName,
+                                       bytes, strlen(bytes), "", 0));
+
+        jsval arg = OBJECT_TO_JSVAL(callTrusted.get());
+        jsval rval;
+        CHECK(JS_CallFunction(cx, NULL, fun, 1, &arg, &rval));
+        CHECK(JSVAL_TO_INT(rval) == 42);
+    }
+
+    return true;
+}
+END_TEST(testChromeBuffer)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -632,17 +632,18 @@ JS_IsBuiltinFunctionConstructor(JSFuncti
  * changes to js_CStringsAreUTF8 after a runtime has been created, and to
  * ensure that "first checks" on runtime creation are run only once.
  */
 #ifdef DEBUG
 static JSBool js_NewRuntimeWasCalled = JS_FALSE;
 #endif
 
 JSRuntime::JSRuntime()
-  : gcChunkAllocator(&defaultGCChunkAllocator)
+  : gcChunkAllocator(&defaultGCChunkAllocator),
+    trustedPrincipals_(NULL)
 {
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&contextList);
     JS_INIT_CLIST(&trapList);
     JS_INIT_CLIST(&watchPointList);
 }
 
 bool
@@ -4147,16 +4148,22 @@ JS_SetContextSecurityCallbacks(JSContext
 JS_PUBLIC_API(JSSecurityCallbacks *)
 JS_GetSecurityCallbacks(JSContext *cx)
 {
   return cx->securityCallbacks
          ? cx->securityCallbacks
          : cx->runtime->securityCallbacks;
 }
 
+JS_PUBLIC_API(void)
+JS_SetTrustedPrincipals(JSRuntime *rt, JSPrincipals *prin)
+{
+    rt->setTrustedPrincipals(prin);
+}
+
 JS_PUBLIC_API(JSFunction *)
 JS_NewFunction(JSContext *cx, JSNative native, uintN nargs, uintN flags,
                JSObject *parent, const char *name)
 {
     JS_THREADSAFE_ASSERT(cx->compartment != cx->runtime->atomsCompartment);
     JSAtom *atom;
 
     CHECK_REQUEST(cx);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2589,16 +2589,31 @@ extern JS_PUBLIC_API(JSSecurityCallbacks
 JS_GetRuntimeSecurityCallbacks(JSRuntime *rt);
 
 extern JS_PUBLIC_API(JSSecurityCallbacks *)
 JS_SetContextSecurityCallbacks(JSContext *cx, JSSecurityCallbacks *callbacks);
 
 extern JS_PUBLIC_API(JSSecurityCallbacks *)
 JS_GetSecurityCallbacks(JSContext *cx);
 
+/*
+ * Code running with "trusted" principals will be given a deeper stack
+ * allocation than ordinary scripts. This allows trusted script to run after
+ * untrusted script has exhausted the stack. This function sets the
+ * runtime-wide trusted principal.
+ *
+ * This principals is not held (via JS_HoldPrincipals/JS_DropPrincipals) since
+ * there is no available JSContext. Instead, the caller must ensure that the
+ * given principals stays valid for as long as 'rt' may point to it. If the
+ * principals would be destroyed before 'rt', JS_SetTrustedPrincipals must be
+ * called again, passing NULL for 'prin'.
+ */
+extern JS_PUBLIC_API(void)
+JS_SetTrustedPrincipals(JSRuntime *rt, JSPrincipals *prin);
+
 /************************************************************************/
 
 /*
  * Functions and scripts.
  */
 extern JS_PUBLIC_API(JSFunction *)
 JS_NewFunction(JSContext *cx, JSNative call, uintN nargs, uintN flags,
                JSObject *parent, const char *name);
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -592,16 +592,22 @@ struct JSRuntime {
     /* Number of threads with active requests and unhandled interrupts. */
     volatile int32      interruptCounter;
 #else
     js::ThreadData      threadData;
 
 #define JS_THREAD_DATA(cx)      (&(cx)->runtime->threadData)
 #endif
 
+  private:
+    JSPrincipals        *trustedPrincipals_;
+  public:
+    void setTrustedPrincipals(JSPrincipals *p) { trustedPrincipals_ = p; }
+    JSPrincipals *trustedPrincipals() const { return trustedPrincipals_; }
+
     /*
      * Object shape (property cache structural type) identifier generator.
      *
      * Type 0 stands for the empty scope, and must not be regenerated due to
      * uint32 wrap-around. Since js_GenerateShape (in jsinterp.cpp) uses
      * atomic pre-increment, the initial value for the first typed non-empty
      * scope will be 1.
      *
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -181,17 +181,17 @@ js_GetArgsProperty(JSContext *cx, StackF
         vp->setInt32(fp->numActualArgs());
     }
     return true;
 }
 
 js::ArgumentsObject *
 ArgumentsObject::create(JSContext *cx, uint32 argc, JSObject &callee)
 {
-    JS_ASSERT(argc <= JS_ARGS_LENGTH_MAX);
+    JS_ASSERT(argc <= StackSpace::ARGS_LENGTH_MAX);
 
     JSObject *proto;
     if (!js_GetClassPrototype(cx, callee.getGlobal(), JSProto_Object, &proto))
         return NULL;
 
     JS_STATIC_ASSERT(NormalArgumentsObject::RESERVED_SLOTS == 2);
     JS_STATIC_ASSERT(StrictArgumentsObject::RESERVED_SLOTS == 2);
     JSObject *obj = js_NewGCObject(cx, FINALIZE_OBJECT2);
@@ -2114,17 +2114,17 @@ js_fun_apply(JSContext *cx, uintN argc, 
     JSObject *aobj = &vp[3].toObject();
     jsuint length;
     if (!js_GetLengthProperty(cx, aobj, &length))
         return false;
 
     LeaveTrace(cx);
 
     /* Step 6. */
-    uintN n = uintN(JS_MIN(length, JS_ARGS_LENGTH_MAX));
+    uintN n = uintN(JS_MIN(length, StackSpace::ARGS_LENGTH_MAX));
 
     InvokeArgsGuard args;
     if (!cx->stack.pushInvokeArgs(cx, n, &args))
         return false;
 
     /* Push fval, obj, and aobj's elements as args. */
     args.calleev() = fval;
     args.thisv() = vp[2];
@@ -2218,17 +2218,17 @@ CallOrConstructBoundFunction(JSContext *
     LeaveTrace(cx);
 
     bool constructing = IsConstructing(vp);
 
     /* 15.3.4.5.1 step 1, 15.3.4.5.2 step 3. */
     uintN argslen;
     const Value *boundArgs = obj->getBoundFunctionArguments(argslen);
 
-    if (argc + argslen > JS_ARGS_LENGTH_MAX) {
+    if (argc + argslen > StackSpace::ARGS_LENGTH_MAX) {
         js_ReportAllocationOverflow(cx);
         return false;
     }
 
     /* 15.3.4.5.1 step 3, 15.3.4.5.2 step 1. */
     JSObject *target = obj->getBoundFunctionTarget();
 
     /* 15.3.4.5.1 step 2. */
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -571,35 +571,16 @@ extern JSObject *
 js_GetArgsObject(JSContext *cx, js::StackFrame *fp);
 
 extern void
 js_PutArgsObject(js::StackFrame *fp);
 
 inline bool
 js_IsNamedLambda(JSFunction *fun) { return (fun->flags & JSFUN_LAMBDA) && fun->atom; }
 
-/*
- * Maximum supported value of arguments.length. It bounds the maximum number of
- * arguments that can be supplied via the second (so-called |argArray|) param
- * to Function.prototype.apply. This value also bounds the number of elements
- * parsed in an array initialiser.
- *
- * The thread's stack is the limiting factor for this number. It is currently
- * 2MB, which fits a little less than 2^19 arguments (once the stack frame,
- * callstack, etc. are included). Pick a max args length that is a little less.
- */
-const uint32 JS_ARGS_LENGTH_MAX = JS_BIT(19) - 1024;
-
-/*
- * JSSLOT_ARGS_LENGTH stores ((argc << 1) | overwritten_flag) as an Int32
- * Value.  Thus (JS_ARGS_LENGTH_MAX << 1) | 1 must be less than JSVAL_INT_MAX.
- */
-JS_STATIC_ASSERT(JS_ARGS_LENGTH_MAX <= JS_BIT(30));
-JS_STATIC_ASSERT(((JS_ARGS_LENGTH_MAX << 1) | 1) <= JSVAL_INT_MAX);
-
 extern JSBool
 js_XDRFunctionObject(JSXDRState *xdr, JSObject **objp);
 
 extern JSBool
 js_fun_apply(JSContext *cx, uintN argc, js::Value *vp);
 
 extern JSBool
 js_fun_call(JSContext *cx, uintN argc, js::Value *vp);
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -620,17 +620,17 @@ RunScript(JSContext *cx, JSScript *scrip
  * when done.  Then push the return value.
  */
 JS_REQUIRES_STACK bool
 Invoke(JSContext *cx, const CallArgs &argsRef, MaybeConstruct construct)
 {
     /* N.B. Must be kept in sync with InvokeSessionGuard::start/invoke */
 
     CallArgs args = argsRef;
-    JS_ASSERT(args.argc() <= JS_ARGS_LENGTH_MAX);
+    JS_ASSERT(args.argc() <= StackSpace::ARGS_LENGTH_MAX);
 
     if (args.calleev().isPrimitive()) {
         js_ReportIsNotFunction(cx, &args.calleev(), ToReportFlags(construct));
         return false;
     }
 
     JSObject &callee = args.callee();
     Class *clasp = callee.getClass();
@@ -741,17 +741,17 @@ InvokeSessionGuard::start(JSContext *cx,
         if (status == mjit::Compile_Error)
             return false;
         if (status != mjit::Compile_Okay)
             break;
         /* Cannot also cache the raw code pointer; it can change. */
 
         /* Hoist dynamic checks from CheckStackAndEnterMethodJIT. */
         JS_CHECK_RECURSION(cx, return false);
-        stackLimit_ = stack.space().getStackLimit(cx);
+        stackLimit_ = stack.space().getStackLimit(cx, REPORT_ERROR);
         if (!stackLimit_)
             return false;
 
         stop_ = script_->code + script_->length - 1;
         JS_ASSERT(*stop_ == JSOP_STOP);
 #endif
 
         /* Cached to avoid canonicalActualArg in InvokeSessionGuard::operator[]. */
@@ -4090,17 +4090,17 @@ BEGIN_CASE(JSOP_FUNAPPLY)
         regs.sp = args.spAfterCall();
         CHECK_INTERRUPT_HANDLER();
         TRACE_0(NativeCallComplete);
         len = JSOP_CALL_LENGTH;
         DO_NEXT_OP(len);
     }
 
     JSScript *newScript = fun->script();
-    if (!cx->stack.pushInlineFrame(cx, regs, args, *callee, fun, newScript, construct, OOMCheck()))
+    if (!cx->stack.pushInlineFrame(cx, regs, args, *callee, fun, newScript, construct))
         goto error;
 
     /* Refresh local js::Interpret state. */
     script = newScript;
     pcCounts = script->pcCounters.get(JSRUNMODE_INTERP);
     argv = regs.fp()->formalArgsEnd() - fun->nargs;
     atoms = script->atomMap.vector;
 
@@ -5305,17 +5305,17 @@ BEGIN_CASE(JSOP_INITELEM)
     /*
      * If rref is a hole, do not call JSObject::defineProperty. In this case,
      * obj must be an array, so if the current op is the last element
      * initialiser, set the array length to one greater than id.
      */
     if (rref.isMagic(JS_ARRAY_HOLE)) {
         JS_ASSERT(obj->isArray());
         JS_ASSERT(JSID_IS_INT(id));
-        JS_ASSERT(jsuint(JSID_TO_INT(id)) < JS_ARGS_LENGTH_MAX);
+        JS_ASSERT(jsuint(JSID_TO_INT(id)) < StackSpace::ARGS_LENGTH_MAX);
         if (js_GetOpcode(cx, script, regs.pc + JSOP_INITELEM_LENGTH) == JSOP_ENDINIT &&
             !js_SetLengthProperty(cx, obj, (jsuint) (JSID_TO_INT(id) + 1))) {
             goto error;
         }
     } else {
         if (!obj->defineProperty(cx, id, rref, NULL, NULL, JSPROP_ENUMERATE))
             goto error;
     }
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -8297,17 +8297,17 @@ Parser::primaryExpr(TokenKind tt, JSBool
 
 #if JS_HAS_GENERATORS
         pn->pn_blockid = tc->blockidGen;
 #endif
 
         matched = tokenStream.matchToken(TOK_RB, TSF_OPERAND);
         if (!matched) {
             for (index = 0; ; index++) {
-                if (index == JS_ARGS_LENGTH_MAX) {
+                if (index == StackSpace::ARGS_LENGTH_MAX) {
                     reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_ARRAY_INIT_TOO_BIG);
                     return NULL;
                 }
 
                 tt = tokenStream.peekToken(TSF_OPERAND);
                 if (tt == TOK_RB) {
                     pn->pn_xflags |= PNX_ENDCOMMA;
                     break;
--- a/js/src/jsscope.cpp
+++ b/js/src/jsscope.cpp
@@ -50,17 +50,16 @@
 #include "jsbit.h"
 #include "jsclist.h"
 #include "jsdhash.h"
 #include "jsutil.h"
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
 #include "jsdbgapi.h"
-#include "jsfun.h"      /* for JS_ARGS_LENGTH_MAX */
 #include "jslock.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsscope.h"
 #include "jsstr.h"
 #include "jstracer.h"
 
 #include "jsdbgapiinlines.h"
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -56,17 +56,16 @@
 #include "jshash.h"
 #include "jsprf.h"
 #include "jsapi.h"
 #include "jsarray.h"
 #include "jsatom.h"
 #include "jsbool.h"
 #include "jsbuiltins.h"
 #include "jscntxt.h"
-#include "jsfun.h"      /* for JS_ARGS_LENGTH_MAX */
 #include "jsgc.h"
 #include "jsinterp.h"
 #include "jslock.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsopcode.h"
 #include "jsregexp.h"
 #include "jsscope.h"
@@ -3096,17 +3095,17 @@ js_String(JSContext *cx, uintN argc, Val
     }
     return true;
 }
 
 static JSBool
 str_fromCharCode(JSContext *cx, uintN argc, Value *vp)
 {
     Value *argv = JS_ARGV(cx, vp);
-    JS_ASSERT(argc <= JS_ARGS_LENGTH_MAX);
+    JS_ASSERT(argc <= StackSpace::ARGS_LENGTH_MAX);
     if (argc == 1) {
         uint16_t code;
         if (!ValueToUint16(cx, argv[0], &code))
             return JS_FALSE;
         if (JSAtom::hasUnitStatic(code)) {
             vp->setString(&JSAtom::unitStatic(code));
             return JS_TRUE;
         }
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -5734,18 +5734,17 @@ SynthesizeFrame(JSContext* cx, const Fra
     regs.sp = fp->slots() + fi.spdist;
     regs.pc = fi.pc;
     if (fi.imacpc)
         fp->setImacropc(fi.imacpc);
 
     /* Push a frame for the call. */
     CallArgs args = CallArgsFromSp(fi.get_argc(), regs.sp);
     cx->stack.pushInlineFrame(cx, regs, args, *callee, newfun, newscript,
-                              MaybeConstructFromBool(fi.is_constructing()),
-                              NoCheck());
+                              MaybeConstructFromBool(fi.is_constructing()));
 
 #ifdef DEBUG
     /* These should be initialized by FlushNativeStackFrame. */
     regs.fp()->thisValue().setMagic(JS_THIS_POISON);
     regs.fp()->setScopeChainNoCallObj(*StackFrame::sInvalidScopeChain);
 #endif
 
     /* Call object will be set by FlushNativeStackFrame. */
@@ -6630,17 +6629,18 @@ static JS_REQUIRES_STACK bool
 ExecuteTree(JSContext* cx, TraceMonitor* tm, TreeFragment* f,
             VMSideExit** innermostNestedGuardp, VMSideExit **lrp)
 {
 #ifdef MOZ_TRACEVIS
     TraceVisStateObj tvso(cx, S_EXECUTE);
 #endif
     JS_ASSERT(f->root == f && f->code());
 
-    if (!ScopeChainCheck(cx, f) || !cx->stack.space().ensureEnoughSpaceToEnterTrace()) {
+    if (!ScopeChainCheck(cx, f) ||
+        !cx->stack.space().ensureEnoughSpaceToEnterTrace(cx)) {
         *lrp = NULL;
         return true;
     }
 
     /* Make sure the global object is sane. */
     JS_ASSERT(f->globalObj->numSlots() <= MAX_GLOBAL_SLOTS);
     JS_ASSERT(f->nGlobalTypes() == f->globalSlots->length());
     JS_ASSERT_IF(f->globalSlots->length() != 0,
--- a/js/src/jsutil.h
+++ b/js/src/jsutil.h
@@ -665,13 +665,23 @@ PodEqual(T *one, T *two, size_t len)
                 return false;
         }
         return true;
     }
 
     return !memcmp(one, two, len * sizeof(T));
 }
 
+
+/*
+ * Ordinarily, a function taking a JSContext* 'cx' paremter reports errors on
+ * the context. In some cases, functions optionally report and indicate this by
+ * taking a nullable 'maybecx' parameter. In some cases, though, a function
+ * always needs a 'cx', but optionally reports. This option is presented by the
+ * MaybeReportError.
+ */
+enum MaybeReportError { REPORT_ERROR = true, DONT_REPORT_ERROR = false };
+
 } /* namespace js */
 
 #endif /* defined(__cplusplus) */
 
 #endif /* jsutil_h___ */
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -394,17 +394,17 @@ ForceFrame::enter()
        return false;
     LeaveTrace(context);
 
     JS_ASSERT(context->compartment == target->compartment());
 
     JSObject *scopeChain = target->getGlobal();
     JS_ASSERT(scopeChain->isNative());
 
-    return context->stack.pushDummyFrame(context, *scopeChain, frame);
+    return context->stack.pushDummyFrame(context, REPORT_ERROR, *scopeChain, frame);
 }
 
 AutoCompartment::AutoCompartment(JSContext *cx, JSObject *target)
     : context(cx),
       origin(cx->compartment),
       target(target),
       destination(target->getCompartment()),
       entered(false)
@@ -419,23 +419,33 @@ AutoCompartment::~AutoCompartment()
 
 bool
 AutoCompartment::enter()
 {
     JS_ASSERT(!entered);
     if (origin != destination) {
         LeaveTrace(context);
 
-        context->compartment = destination;
         JSObject *scopeChain = target->getGlobal();
         JS_ASSERT(scopeChain->isNative());
 
         frame.construct();
-        if (!context->stack.pushDummyFrame(context, *scopeChain, &frame.ref())) {
+
+        /*
+         * Set the compartment eagerly so that pushDummyFrame associates the
+         * resource allocation request with 'destination' instead of 'origin'.
+         * (This is important when content has overflowed the stack and chrome
+         * is preparing to run JS to throw up a slow script dialog.) However,
+         * if an exception is thrown, we need it to be in origin's compartment
+         * so be careful to only report after restoring.
+         */
+        context->compartment = destination;
+        if (!context->stack.pushDummyFrame(context, DONT_REPORT_ERROR, *scopeChain, &frame.ref())) {
             context->compartment = origin;
+            js_ReportOverRecursed(context);
             return false;
         }
 
         if (context->isExceptionPending())
             context->wrapPendingException();
     }
     entered = true;
     return true;
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -197,19 +197,18 @@ stubs::SlowNew(VMFrame &f, uint32 argc)
 /*
  * HitStackQuota is called after the early prologue pushing the new frame would
  * overflow f.stackLimit.
  */
 void JS_FASTCALL
 stubs::HitStackQuota(VMFrame &f)
 {
     /* Include space to push another frame. */
-    uintN nvals = f.fp()->script()->nslots + VALUES_PER_STACK_FRAME;
-    JS_ASSERT(f.regs.sp == f.fp()->base());
-    if (f.cx->stack.space().tryBumpLimit(NULL, f.regs.sp, nvals, &f.stackLimit))
+    f.stackLimit = f.cx->stack.space().getStackLimit(f.cx, DONT_REPORT_ERROR);
+    if (f.stackLimit)
         return;
 
     f.cx->stack.popFrameAfterOverflow();
     js_ReportOverRecursed(f.cx);
     THROW();
 }
 
 /*
@@ -235,24 +234,25 @@ stubs::FixupArity(VMFrame &f, uint32 nac
     JSScript *script         = fun->script();
     void *ncode              = oldfp->nativeReturnAddress();
 
     /* Pop the inline frame. */
     f.regs.popPartialFrame((Value *)oldfp);
 
     /* Reserve enough space for a callee frame. */
     CallArgs args = CallArgsFromSp(nactual, f.regs.sp);
-    StackFrame *fp = cx->stack.getFixupFrame(cx, f.regs, args, fun, script, ncode,
-                                             construct, LimitCheck(&f.stackLimit));
+    StackFrame *fp = cx->stack.getFixupFrame(cx, DONT_REPORT_ERROR, args, fun,
+                                             script, ncode, construct, &f.stackLimit);
     if (!fp) {
         /*
          * The PC is not coherent with the current frame, so fix it up for
          * exception handling.
          */
         f.regs.pc = f.jit()->nativeToPC(ncode);
+        js_ReportOverRecursed(cx);
         THROWV(NULL);
     }
 
     /* The caller takes care of assigning fp to regs. */
     return fp;
 }
 
 void * JS_FASTCALL
@@ -311,18 +311,17 @@ UncachedInlineCall(VMFrame &f, MaybeCons
 {
     JSContext *cx = f.cx;
     CallArgs args = CallArgsFromSp(argc, f.regs.sp);
     JSObject &callee = args.callee();
     JSFunction *newfun = callee.getFunctionPrivate();
     JSScript *newscript = newfun->script();
 
     /* Get pointer to new frame/slots, prepare arguments. */
-    LimitCheck check(&f.stackLimit);
-    if (!cx->stack.pushInlineFrame(cx, f.regs, args, callee, newfun, newscript, construct, check))
+    if (!cx->stack.pushInlineFrame(cx, f.regs, args, callee, newfun, newscript, construct, &f.stackLimit))
         return false;
 
     /* Scope with a call object parented by callee's parent. */
     if (newfun->isHeavyweight() && !js::CreateFunCallObject(cx, f.fp()))
         return false;
 
     /* Try to compile if not already compiled. */
     if (newscript->getJITStatus(f.fp()->isConstructing()) == JITScript_None) {
--- a/js/src/methodjit/MethodJIT.cpp
+++ b/js/src/methodjit/MethodJIT.cpp
@@ -704,17 +704,17 @@ mjit::EnterMethodJIT(JSContext *cx, Stac
     return ok;
 }
 
 static inline JSBool
 CheckStackAndEnterMethodJIT(JSContext *cx, StackFrame *fp, void *code)
 {
     JS_CHECK_RECURSION(cx, return false);
 
-    Value *stackLimit = cx->stack.space().getStackLimit(cx);
+    Value *stackLimit = cx->stack.space().getStackLimit(cx, REPORT_ERROR);
     if (!stackLimit)
         return false;
 
     return EnterMethodJIT(cx, fp, code, stackLimit);
 }
 
 JSBool
 mjit::JaegerShot(JSContext *cx)
--- a/js/src/methodjit/MonoIC.cpp
+++ b/js/src/methodjit/MonoIC.cpp
@@ -1091,17 +1091,17 @@ ic::SplatApplyArgs(VMFrame &f)
                 JSObject *aobj = &fp->argsObj();
 
                 /* Steps 4-5 */
                 uintN length;
                 if (!js_GetLengthProperty(cx, aobj, &length))
                     THROWV(false);
 
                 /* Step 6. */
-                n = Min(length, JS_ARGS_LENGTH_MAX);
+                n = Min(length, StackSpace::ARGS_LENGTH_MAX);
 
                 if (!BumpStack(f, n))
                     THROWV(false);
 
                 /* Steps 7-8 */
                 Value *argv = JS_ARGV(cx, &vp[1]);  /* vp[1] is the callee */
                 f.regs.sp += n;  /* GetElements may reenter, so inc early. */
                 if (!GetElements(cx, aobj, n, argv))
@@ -1146,17 +1146,17 @@ ic::SplatApplyArgs(VMFrame &f)
     JSObject *aobj = &vp[3].toObject();
     jsuint length;
     if (!js_GetLengthProperty(cx, aobj, &length))
         THROWV(false);
 
     JS_ASSERT(!JS_ON_TRACE(cx));
 
     /* Step 6. */
-    uintN n = uintN(JS_MIN(length, JS_ARGS_LENGTH_MAX));
+    uintN n = uintN(JS_MIN(length, StackSpace::ARGS_LENGTH_MAX));
 
     intN delta = n - 1;
     if (delta > 0 && !BumpStack(f, delta))
         THROWV(false);
     f.regs.sp += delta;
 
     /* Steps 7-8. */
     if (!GetElements(cx, aobj, n, f.regs.sp - n))
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -1294,17 +1294,17 @@ stubs::InitElem(VMFrame &f, uint32 last)
     /*
      * If rref is a hole, do not call JSObject::defineProperty. In this case,
      * obj must be an array, so if the current op is the last element
      * initialiser, set the array length to one greater than id.
      */
     if (rref.isMagic(JS_ARRAY_HOLE)) {
         JS_ASSERT(obj->isArray());
         JS_ASSERT(JSID_IS_INT(id));
-        JS_ASSERT(jsuint(JSID_TO_INT(id)) < JS_ARGS_LENGTH_MAX);
+        JS_ASSERT(jsuint(JSID_TO_INT(id)) < StackSpace::ARGS_LENGTH_MAX);
         if (last && !js_SetLengthProperty(cx, obj, (jsuint) (JSID_TO_INT(id) + 1)))
             THROW();
     } else {
         if (!obj->defineProperty(cx, id, rref, NULL, NULL, JSPROP_ENUMERATE))
             THROW();
     }
 }
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4792,16 +4792,23 @@ EnableStackWalkingAssertion(JSContext *c
 #ifdef DEBUG
     cx->stackIterAssertionEnabled = JSVAL_TO_BOOLEAN(JS_ARGV(cx, vp)[0]);
 #endif
 
     JS_SET_RVAL(cx, vp, JSVAL_VOID);
     return true;
 }
 
+static JSBool
+GetMaxArgs(JSContext *cx, uintN arg, jsval *vp)
+{
+    JS_SET_RVAL(cx, vp, INT_TO_JSVAL(StackSpace::ARGS_LENGTH_MAX));
+    return JS_TRUE;
+}
+
 static JSFunctionSpec shell_functions[] = {
     JS_FN("version",        Version,        0,0),
     JS_FN("revertVersion",  RevertVersion,  0,0),
     JS_FN("options",        Options,        0,0),
     JS_FN("load",           Load,           1,0),
     JS_FN("evaluate",       Evaluate,       1,0),
     JS_FN("run",            Run,            1,0),
     JS_FN("readline",       ReadLine,       0,0),
@@ -4892,16 +4899,17 @@ static JSFunctionSpec shell_functions[] 
 #ifdef JS_METHODJIT
     JS_FN("mjitcodestats",  MJitCodeStats,  0,0),
     JS_FN("mjitdatastats",  MJitDataStats,  0,0),
 #endif
     JS_FN("stringstats",    StringStats,    0,0),
     JS_FN("newGlobal",      NewGlobal,      1,0),
     JS_FN("parseLegacyJSON",ParseLegacyJSON,1,0),
     JS_FN("enableStackWalkingAssertion",EnableStackWalkingAssertion,1,0),
+    JS_FN("getMaxArgs",     GetMaxArgs,     0,0),
     JS_FS_END
 };
 
 static const char shell_help_header[] =
 "Command                  Description\n"
 "=======                  ===========\n";
 
 static const char *const shell_help_messages[] = {
@@ -5037,16 +5045,17 @@ static const char *const shell_help_mess
 "                         new compartment if kind === 'new-compartment'",
 "parseLegacyJSON(str)     Parse str as legacy JSON, returning the result if the\n"
 "                         parse succeeded and throwing a SyntaxError if not.",
 "enableStackWalkingAssertion(enabled)\n"
 "  Enables or disables a particularly expensive assertion in stack-walking\n"
 "  code.  If your test isn't ridiculously thorough, such that performing this\n"
 "  assertion increases test duration by an order of magnitude, you shouldn't\n"
 "  use this.",
+"getMaxArgs()             Return the maximum number of supported args for a call."
 
 /* Keep these last: see the static assertion below. */
 #ifdef MOZ_PROFILING
 "startProfiling()         Start a profiling session.\n"
 "                         Profiler must be running with programatic sampling",
 "stopProfiling()          Stop a running profiling session\n"
 #endif
 };
@@ -6041,16 +6050,31 @@ MaybeOverrideOutFileFromEnv(const char* 
                             FILE** outFile)
 {
     const char* outPath = getenv(envVar);
     if (!outPath || !*outPath || !(*outFile = fopen(outPath, "w"))) {
         *outFile = defaultOut;
     }
 }
 
+JSBool
+ShellPrincipalsSubsume(JSPrincipals *, JSPrincipals *)
+{
+    return JS_TRUE;
+}
+
+JSPrincipals shellTrustedPrincipals = {
+    (char *)"[shell trusted principals]",
+    NULL,
+    NULL,
+    1,
+    NULL, /* nobody should be destroying this */
+    ShellPrincipalsSubsume
+};
+
 int
 main(int argc, char **argv, char **envp)
 {
 #ifdef DEBUG
     /* Check the allocation count first, or else we'll miss allocations. */
     for (int i = 0; i < argc; i++)
     {
       if (strlen(argv[i]) == 2 && argv[i][0] == '-' && argv[i][1] == 'A')
@@ -6127,16 +6151,18 @@ main(int argc, char **argv, char **envp)
     extern int CALIBRATION_DELAY_COUNT;
     CALIBRATION_DELAY_COUNT = 0;
 #endif
 
     rt = JS_NewRuntime(160L * 1024L * 1024L);
     if (!rt)
         return 1;
 
+    JS_SetTrustedPrincipals(rt, &shellTrustedPrincipals);
+
     if (!InitWatchdog(rt))
         return 1;
 
     cx = NewContext(rt);
     if (!cx)
         return 1;
 
     JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_COMPARTMENT);
--- a/js/src/tests/js1_5/Array/jstests.list
+++ b/js/src/tests/js1_5/Array/jstests.list
@@ -14,17 +14,16 @@ script regress-310351.js
 script regress-311515.js
 script regress-313153.js
 script regress-315509-01.js
 skip-if(xulRuntime.XPCOMABI.match(/x86_64/)) script regress-330812.js # No test results
 script regress-345961.js
 script regress-348810.js
 script regress-350256-01.js
 script regress-350256-02.js
-silentfail script regress-350256-03.js
 script regress-360681-01.js
 script regress-360681-02.js
 script regress-364104.js
 script regress-422286.js
 script regress-424954.js
 script regress-451483.js
 script regress-451906.js
 script regress-456845.js
--- a/js/src/tests/js1_5/Array/regress-350256-01.js
+++ b/js/src/tests/js1_5/Array/regress-350256-01.js
@@ -32,17 +32,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 350256;
-var summary = 'Array.apply maximum arguments: 2^16';
+var summary = 'Array.apply maximum arguments';
 var actual = '';
 var expect = '';
 
 
 //-----------------------------------------------------------------------------
 test(Math.pow(2, 16));
 //-----------------------------------------------------------------------------
 
--- a/js/src/tests/js1_5/Array/regress-350256-02.js
+++ b/js/src/tests/js1_5/Array/regress-350256-02.js
@@ -32,23 +32,24 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 350256;
-var summary = 'Array.apply maximum arguments: 2^19 - 1024';
+var summary = 'Array.apply maximum arguments';
 var actual = '';
 var expect = '';
 
 
 //-----------------------------------------------------------------------------
-test(Math.pow(2, 19) - 1024);
+if (getMaxArgs)
+    test(getMaxArgs());
 //-----------------------------------------------------------------------------
 
 function test(length)
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
  
deleted file mode 100644
--- a/js/src/tests/js1_5/Array/regress-350256-03.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is JavaScript Engine testing utilities.
- *
- * The Initial Developer of the Original Code is
- * Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2006
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s): Bertrand Le Roy
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-//-----------------------------------------------------------------------------
-var BUGNUMBER = 350256;
-var summary = 'Array.apply maximum arguments: 2^19-1024';
-var actual = '';
-var expect = '';
-
-expectExitCode(0);
-expectExitCode(5);
-
-//-----------------------------------------------------------------------------
-test(Math.pow(2, 19)-1024);
-//-----------------------------------------------------------------------------
-
-function test(length)
-{
-  enterFunc ('test');
-  printBugNumber(BUGNUMBER);
-  printStatus (summary);
-
-  var a = new Array();
-  a[length - 2] = 'length-2';
-  a[length - 1] = 'length-1';
-
-  var b = Array.apply(null, a);
-
-  expect = length + ',length-2,length-1';
-  actual = b.length + "," + b[length - 2] + "," + b[length - 1];
-  reportCompare(expect, actual, summary);
-
-  function f() {
-    return arguments.length + "," + arguments[length - 2] + "," +
-      arguments[length - 1];
-  }
-
-  expect = length + ',length-2,length-1';
-  actual = f.apply(null, a);
-
-  reportCompare(expect, actual, summary);
-
-  exitFunc ('test');
-}
--- a/js/src/vm/ArgumentsObject-inl.h
+++ b/js/src/vm/ArgumentsObject-inl.h
@@ -53,17 +53,17 @@ ArgumentsObject::setInitialLength(uint32
     JS_ASSERT((getSlot(INITIAL_LENGTH_SLOT).toInt32() >> PACKED_BITS_COUNT) == int32(length));
     JS_ASSERT(!hasOverriddenLength());
 }
 
 inline uint32
 ArgumentsObject::initialLength() const
 {
     uint32 argc = uint32(getSlot(INITIAL_LENGTH_SLOT).toInt32()) >> PACKED_BITS_COUNT;
-    JS_ASSERT(argc <= JS_ARGS_LENGTH_MAX);
+    JS_ASSERT(argc <= StackSpace::ARGS_LENGTH_MAX);
     return argc;
 }
 
 inline void
 ArgumentsObject::markLengthOverridden()
 {
     getSlotRef(INITIAL_LENGTH_SLOT).getInt32Ref() |= LENGTH_OVERRIDDEN_BIT;
 }
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -139,26 +139,25 @@ StackFrame::resetCallFrame(JSScript *scr
 
     JS_ASSERT(exec.fun == callee().getFunctionPrivate());
     scopeChain_ = callee().getParent();
 
     SetValueRangeToUndefined(slots(), script->nfixed);
 }
 
 inline void
-StackFrame::initJitFrameCallerHalf(JSContext *cx, StackFrame::Flags flags,
-                                    void *ncode)
+StackFrame::initJitFrameCallerHalf(StackFrame *prev, StackFrame::Flags flags, void *ncode)
 {
     JS_ASSERT((flags & ~(CONSTRUCTING |
                          FUNCTION |
                          OVERFLOW_ARGS |
                          UNDERFLOW_ARGS)) == 0);
 
     flags_ = FUNCTION | flags;
-    prev_ = cx->fp();
+    prev_ = prev;
     ncode_ = ncode;
 }
 
 /*
  * The "early prologue" refers to either the fast path or arity check path up
  * to the "late prologue".
  */
 inline void
@@ -171,25 +170,21 @@ StackFrame::initJitFrameEarlyPrologue(JS
 
 /*
  * The "late prologue" (in generatePrologue) extends from the join point of the
  * fast path and arity check to where the call object is (possibly) created.
  */
 inline bool
 StackFrame::initJitFrameLatePrologue(JSContext *cx, Value **limit)
 {
-    uintN nvals = script()->nslots + VALUES_PER_STACK_FRAME;
-    Value *required = (Value *)this + nvals;
-    if (required >= *limit) {
-        ContextStack &stack = cx->stack;
-        if (!stack.space().tryBumpLimit(NULL, slots(), nvals, limit)) {
-            stack.popFrameAfterOverflow();
-            js_ReportOverRecursed(cx);
-            return false;
-        }
+    *limit = cx->stack.space().getStackLimit(cx, DONT_REPORT_ERROR);
+    if (!*limit) {
+        cx->stack.popFrameAfterOverflow();
+        js_ReportOverRecursed(cx);
+        return false;
     }
 
     SetValueRangeToUndefined(slots(), script()->nfixed);
     return true;
 }
 
 inline Value &
 StackFrame::canonicalActualArg(uintN i) const
@@ -378,174 +373,146 @@ StackFrame::markActivationObjectsAsPut()
         }
     }
 }
 
 /*****************************************************************************/
 
 #ifdef JS_TRACER
 JS_ALWAYS_INLINE bool
-StackSpace::ensureEnoughSpaceToEnterTrace()
+StackSpace::ensureEnoughSpaceToEnterTrace(JSContext *cx)
 {
     ptrdiff_t needed = TraceNativeStorage::MAX_NATIVE_STACK_SLOTS +
                        TraceNativeStorage::MAX_CALL_STACK_ENTRIES * VALUES_PER_STACK_FRAME;
-#ifdef XP_WIN
-    return ensureSpace(NULL, firstUnused(), needed);
-#else
-    return end_ - firstUnused() > needed;
-#endif
+    return ensureSpace(cx, DONT_REPORT_ERROR, firstUnused(), needed);
 }
 #endif
 
 STATIC_POSTCONDITION(!return || ubound(from) >= nvals)
 JS_ALWAYS_INLINE bool
-StackSpace::ensureSpace(JSContext *maybecx, Value *from, ptrdiff_t nvals) const
+StackSpace::ensureSpace(JSContext *cx, MaybeReportError report, Value *from, ptrdiff_t nvals) const
 {
+    assertInvariants();
     JS_ASSERT(from >= firstUnused());
 #ifdef XP_WIN
     JS_ASSERT(from <= commitEnd_);
-    if (commitEnd_ - from < nvals)
-        return bumpCommit(maybecx, from, nvals);
+#endif
+    if (JS_UNLIKELY(conservativeEnd_ - from < nvals))
+        return ensureSpaceSlow(cx, report, from, nvals);
     return true;
-#else
-    if (end_ - from < nvals) {
-        js_ReportOverRecursed(maybecx);
-        return false;
-    }
-    return true;
-#endif
 }
 
 inline Value *
-StackSpace::getStackLimit(JSContext *cx)
+StackSpace::getStackLimit(JSContext *cx, MaybeReportError report)
 {
-    Value *limit;
-#ifdef XP_WIN
-    limit = commitEnd_;
-#else
-    limit = end_;
-#endif
-
-    /* See getStackLimit comment in Stack.h. */
     FrameRegs &regs = cx->regs();
-    uintN minSpace = regs.fp()->numSlots() + VALUES_PER_STACK_FRAME;
-    if (regs.sp + minSpace > limit) {
-        js_ReportOverRecursed(cx);
-        return NULL;
-    }
-
-    return limit;
+    uintN nvals = regs.fp()->numSlots() + VALUES_PER_STACK_FRAME;
+    return ensureSpace(cx, report, regs.sp, nvals)
+           ? conservativeEnd_
+           : NULL;
 }
 
 /*****************************************************************************/
 
-JS_ALWAYS_INLINE bool
-OOMCheck::operator()(JSContext *cx, StackSpace &space, Value *from, uintN nvals)
+JS_ALWAYS_INLINE StackFrame *
+ContextStack::getCallFrame(JSContext *cx, MaybeReportError report, const CallArgs &args,
+                           JSFunction *fun, JSScript *script, StackFrame::Flags *flags) const
 {
-    return space.ensureSpace(cx, from, nvals);
-}
+    JS_ASSERT(fun->script() == script);
+    uintN nformal = fun->nargs;
 
-JS_ALWAYS_INLINE bool
-LimitCheck::operator()(JSContext *cx, StackSpace &space, Value *from, uintN nvals)
-{
+    Value *firstUnused = args.end();
+    JS_ASSERT(firstUnused == space().firstUnused());
+
     /*
      * Include an extra sizeof(StackFrame) to satisfy the method-jit
      * stackLimit invariant.
      */
-    nvals += VALUES_PER_STACK_FRAME;
-    JS_ASSERT(from < *limit);
-    if (*limit - from >= ptrdiff_t(nvals))
-        return true;
-    return space.tryBumpLimit(cx, from, nvals, limit);
-}
-
-template <class Check>
-JS_ALWAYS_INLINE StackFrame *
-ContextStack::getCallFrame(JSContext *cx, const CallArgs &args,
-                           JSFunction *fun, JSScript *script,
-                           StackFrame::Flags *flags, Check check) const
-{
-    JS_ASSERT(fun->script() == script);
-    JS_ASSERT(space().firstUnused() == args.end());
-
-    Value *firstUnused = args.end();
-    uintN nvals = VALUES_PER_STACK_FRAME + script->nslots;
-    uintN nformal = fun->nargs;
+    uintN nvals = 2 * VALUES_PER_STACK_FRAME + script->nslots;
 
     /* Maintain layout invariant: &formalArgs[0] == ((Value *)fp) - nformal. */
 
     if (args.argc() == nformal) {
-        if (JS_UNLIKELY(!check(cx, space(), firstUnused, nvals)))
+        if (!space().ensureSpace(cx, report, firstUnused, nvals))
             return NULL;
         return reinterpret_cast<StackFrame *>(firstUnused);
     }
 
     if (args.argc() < nformal) {
         *flags = StackFrame::Flags(*flags | StackFrame::UNDERFLOW_ARGS);
         uintN nmissing = nformal - args.argc();
-        if (JS_UNLIKELY(!check(cx, space(), firstUnused, nmissing + nvals)))
+        if (!space().ensureSpace(cx, report, firstUnused, nmissing + nvals))
             return NULL;
         SetValueRangeToUndefined(firstUnused, nmissing);
         return reinterpret_cast<StackFrame *>(firstUnused + nmissing);
     }
 
     *flags = StackFrame::Flags(*flags | StackFrame::OVERFLOW_ARGS);
     uintN ncopy = 2 + nformal;
-    if (JS_UNLIKELY(!check(cx, space(), firstUnused, ncopy + nvals)))
+    if (!space().ensureSpace(cx, report, firstUnused, ncopy + nvals))
         return NULL;
-
     Value *dst = firstUnused;
     Value *src = args.base();
     PodCopy(dst, src, ncopy);
     return reinterpret_cast<StackFrame *>(firstUnused + ncopy);
 }
 
-template <class Check>
 JS_ALWAYS_INLINE bool
 ContextStack::pushInlineFrame(JSContext *cx, FrameRegs &regs, const CallArgs &args,
                               JSObject &callee, JSFunction *fun, JSScript *script,
-                              MaybeConstruct construct, Check check)
+                              MaybeConstruct construct)
 {
     JS_ASSERT(onTop());
     JS_ASSERT(&regs == &seg_->regs());
     JS_ASSERT(regs.sp == args.end());
     /* Cannot assert callee == args.callee() since this is called from LeaveTree. */
     JS_ASSERT(callee.getFunctionPrivate() == fun);
     JS_ASSERT(fun->script() == script);
 
     StackFrame::Flags flags = ToFrameFlags(construct);
-    StackFrame *fp = getCallFrame(cx, args, fun, script, &flags, check);
+    StackFrame *fp = getCallFrame(cx, REPORT_ERROR, args, fun, script, &flags);
     if (!fp)
         return false;
 
     /* Initialize frame, locals, regs. */
     fp->initCallFrame(cx, callee, fun, script, args.argc(), flags);
     regs.prepareToRun(*fp, script);
     return true;
 }
 
+JS_ALWAYS_INLINE bool
+ContextStack::pushInlineFrame(JSContext *cx, FrameRegs &regs, const CallArgs &args,
+                              JSObject &callee, JSFunction *fun, JSScript *script,
+                              MaybeConstruct construct, Value **stackLimit)
+{
+    if (!pushInlineFrame(cx, regs, args, callee, fun, script, construct))
+        return false;
+    *stackLimit = space().conservativeEnd_;
+    return true;
+}
+
 JS_ALWAYS_INLINE StackFrame *
-ContextStack::getFixupFrame(JSContext *cx, FrameRegs &regs, const CallArgs &args,
-                            JSFunction *fun, JSScript *script, void *ncode,
-                            MaybeConstruct construct, LimitCheck check)
+ContextStack::getFixupFrame(JSContext *cx, MaybeReportError report,
+                            const CallArgs &args, JSFunction *fun, JSScript *script,
+                            void *ncode, MaybeConstruct construct, Value **stackLimit)
 {
     JS_ASSERT(onTop());
-    JS_ASSERT(&regs == &cx->regs());
-    JS_ASSERT(regs.sp == args.end());
     JS_ASSERT(args.callee().getFunctionPrivate() == fun);
     JS_ASSERT(fun->script() == script);
 
     StackFrame::Flags flags = ToFrameFlags(construct);
-    StackFrame *fp = getCallFrame(cx, args, fun, script, &flags, check);
+    StackFrame *fp = getCallFrame(cx, report, args, fun, script, &flags);
     if (!fp)
         return NULL;
 
     /* Do not init late prologue or regs; this is done by jit code. */
-    fp->initJitFrameCallerHalf(cx, flags, ncode);
+    fp->initJitFrameCallerHalf(cx->fp(), flags, ncode);
     fp->initJitFrameEarlyPrologue(fun, args.argc());
+
+    *stackLimit = space().conservativeEnd_;
     return fp;
 }
 
 JS_ALWAYS_INLINE void
 ContextStack::popInlineFrame(FrameRegs &regs)
 {
     JS_ASSERT(onTop());
     JS_ASSERT(&regs == &seg_->regs());
@@ -643,14 +610,14 @@ ArgumentsObject::getElements(uint32 star
         return true;
     }
 
     /* If we're on trace, there's no canonical location for elements: fail. */
     if (fp == JS_ARGUMENTS_OBJECT_ON_TRACE)
         return false;
 
     /* Otherwise, element values are on the stack. */
-    JS_ASSERT(fp->numActualArgs() <= JS_ARGS_LENGTH_MAX);
+    JS_ASSERT(fp->numActualArgs() <= StackSpace::ARGS_LENGTH_MAX);
     return fp->forEachCanonicalActualArg(detail::CopyNonHoleArgsTo(this, vp), start, count);
 }
 
 } /* namespace js */
 #endif /* Stack_inl_h__ */
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -297,55 +297,66 @@ void
 StackSegment::popCall()
 {
     calls_ = calls_->prev_;
 }
 
 /*****************************************************************************/
 
 StackSpace::StackSpace()
-  : base_(NULL),
+  : seg_(NULL),
+    base_(NULL),
+    conservativeEnd_(NULL),
+#ifdef XP_WIN
     commitEnd_(NULL),
-    end_(NULL),
-    seg_(NULL)
-{}
+#endif
+    defaultEnd_(NULL),
+    trustedEnd_(NULL)
+{
+    assertInvariants();
+}
 
 bool
 StackSpace::init()
 {
     void *p;
 #ifdef XP_WIN
     p = VirtualAlloc(NULL, CAPACITY_BYTES, MEM_RESERVE, PAGE_READWRITE);
     if (!p)
         return false;
     void *check = VirtualAlloc(p, COMMIT_BYTES, MEM_COMMIT, PAGE_READWRITE);
     if (p != check)
         return false;
     base_ = reinterpret_cast<Value *>(p);
-    commitEnd_ = base_ + COMMIT_VALS;
-    end_ = base_ + CAPACITY_VALS;
+    conservativeEnd_ = commitEnd_ = base_ + COMMIT_VALS;
+    trustedEnd_ = base_ + CAPACITY_VALS;
+    defaultEnd_ = trustedEnd_ - BUFFER_VALS;
 #elif defined(XP_OS2)
     if (DosAllocMem(&p, CAPACITY_BYTES, PAG_COMMIT | PAG_READ | PAG_WRITE | OBJ_ANY) &&
         DosAllocMem(&p, CAPACITY_BYTES, PAG_COMMIT | PAG_READ | PAG_WRITE))
         return false;
     base_ = reinterpret_cast<Value *>(p);
-    end_ = commitEnd_ = base_ + CAPACITY_VALS;
+    trustedEnd_ = base_ + CAPACITY_VALS;
+    conservativeEnd_ = defaultEnd_ = trustedEnd_ - BUFFER_VALS;
 #else
     JS_ASSERT(CAPACITY_BYTES % getpagesize() == 0);
     p = mmap(NULL, CAPACITY_BYTES, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
     if (p == MAP_FAILED)
         return false;
     base_ = reinterpret_cast<Value *>(p);
-    end_ = commitEnd_ = base_ + CAPACITY_VALS;
+    trustedEnd_ = base_ + CAPACITY_VALS;
+    conservativeEnd_ = defaultEnd_ = trustedEnd_ - BUFFER_VALS;
 #endif
+    assertInvariants();
     return true;
 }
 
 StackSpace::~StackSpace()
 {
+    assertInvariants();
     JS_ASSERT(!seg_);
     if (!base_)
         return;
 #ifdef XP_WIN
     VirtualFree(base_, (commitEnd_ - base_) * sizeof(Value), MEM_DECOMMIT);
     VirtualFree(base_, 0, MEM_RELEASE);
 #elif defined(XP_OS2)
     DosFreeMem(base_);
@@ -397,65 +408,85 @@ StackSpace::mark(JSTracer *trc)
             js_TraceStackFrame(trc, fp);
             slotsEnd = (Value *)fp;
         }
         MarkStackRangeConservatively(trc, seg->slotsBegin(), slotsEnd);
         nextSegEnd = (Value *)seg;
     }
 }
 
-#ifdef XP_WIN
 JS_FRIEND_API(bool)
-StackSpace::bumpCommit(JSContext *maybecx, Value *from, ptrdiff_t nvals) const
+StackSpace::ensureSpaceSlow(JSContext *cx, MaybeReportError report,
+                            Value *from, ptrdiff_t nvals) const
 {
-    if (end_ - from < nvals) {
-        js_ReportOverRecursed(maybecx);
+    assertInvariants();
+
+    bool trusted = !cx->compartment ||
+                   cx->compartment->principals == cx->runtime->trustedPrincipals();
+    Value *end = trusted ? trustedEnd_ : defaultEnd_;
+
+    /*
+     * conservativeEnd_ must stay below defaultEnd_: if conservativeEnd_ were
+     * to be bumped past defaultEnd_, untrusted JS would be able to consume the
+     * buffer space at the end of the stack reserved for trusted JS.
+     */
+
+    if (end - from < nvals) {
+        if (report)
+            js_ReportOverRecursed(cx);
         return false;
     }
 
-    Value *newCommit = commitEnd_;
-    Value *request = from + nvals;
+#ifdef XP_WIN
+    if (commitEnd_ - from < nvals) {
+        Value *newCommit = commitEnd_;
+        Value *request = from + nvals;
 
-    /* Use a dumb loop; will probably execute once. */
-    JS_ASSERT((end_ - newCommit) % COMMIT_VALS == 0);
-    do {
-        newCommit += COMMIT_VALS;
-        JS_ASSERT((end_ - newCommit) >= 0);
-    } while (newCommit < request);
+        /* Use a dumb loop; will probably execute once. */
+        JS_ASSERT((trustedEnd_ - newCommit) % COMMIT_VALS == 0);
+        do {
+            newCommit += COMMIT_VALS;
+            JS_ASSERT((trustedEnd_ - newCommit) >= 0);
+        } while (newCommit < request);
 
-    /* The cast is safe because CAPACITY_BYTES is small. */
-    int32 size = static_cast<int32>(newCommit - commitEnd_) * sizeof(Value);
+        /* The cast is safe because CAPACITY_BYTES is small. */
+        int32 size = static_cast<int32>(newCommit - commitEnd_) * sizeof(Value);
 
-    if (!VirtualAlloc(commitEnd_, size, MEM_COMMIT, PAGE_READWRITE)) {
-        js_ReportOverRecursed(maybecx);
-        return false;
+        if (!VirtualAlloc(commitEnd_, size, MEM_COMMIT, PAGE_READWRITE)) {
+            if (report)
+                js_ReportOverRecursed(cx);
+            return false;
+        }
+
+        commitEnd_ = newCommit;
+        conservativeEnd_ = Min(commitEnd_, defaultEnd_);
+        assertInvariants();
     }
+#endif
 
-    commitEnd_ = newCommit;
     return true;
 }
-#endif
 
 bool
-StackSpace::tryBumpLimit(JSContext *maybecx, Value *from, uintN nvals, Value **limit)
+StackSpace::tryBumpLimit(JSContext *cx, Value *from, uintN nvals, Value **limit)
 {
-    if (!ensureSpace(maybecx, from, nvals))
+    if (!ensureSpace(cx, REPORT_ERROR, from, nvals))
         return false;
-#ifdef XP_WIN
-    *limit = commitEnd_;
-#else
-    *limit = end_;
-#endif
+    *limit = conservativeEnd_;
     return true;
 }
 
 size_t
 StackSpace::committedSize()
 {
+#ifdef XP_WIN
     return (commitEnd_ - base_) * sizeof(Value);
+#else
+    return (trustedEnd_ - base_) * sizeof(Value);
+#endif
 }
 
 /*****************************************************************************/
 
 ContextStack::ContextStack(JSContext *cx)
   : seg_(NULL),
     space_(&JS_THREAD_DATA(cx)->stackSpace),
     cx_(cx)
@@ -511,27 +542,28 @@ ContextStack::containsSlow(const StackFr
  * (so that it can be extended to push a frame and/or arguments) by potentially
  * pushing a StackSegment. The 'pushedSeg' outparam indicates whether such a
  * segment was pushed (and hence whether the caller needs to call popSegment).
  *
  * Additionally, to minimize calls to ensureSpace, ensureOnTop ensures that
  * there is space for nvars slots on top of the stack.
  */
 Value *
-ContextStack::ensureOnTop(JSContext *cx, uintN nvars, MaybeExtend extend, bool *pushedSeg)
+ContextStack::ensureOnTop(JSContext *cx, MaybeReportError report, uintN nvars,
+                          MaybeExtend extend, bool *pushedSeg)
 {
     Value *firstUnused = space().firstUnused();
 
     if (onTop() && extend) {
-        if (!space().ensureSpace(cx, firstUnused, nvars))
+        if (!space().ensureSpace(cx, report, firstUnused, nvars))
             return NULL;
         return firstUnused;
     }
 
-    if (!space().ensureSpace(cx, firstUnused, VALUES_PER_STACK_SEGMENT + nvars))
+    if (!space().ensureSpace(cx, report, firstUnused, VALUES_PER_STACK_SEGMENT + nvars))
         return NULL;
 
     FrameRegs *regs;
     CallArgsList *calls;
     if (seg_ && extend) {
         regs = seg_->maybeRegs();
         calls = seg_->maybeCalls();
     } else {
@@ -554,17 +586,17 @@ ContextStack::popSegment()
     if (!seg_)
         cx_->maybeMigrateVersionOverride();
 }
 
 bool
 ContextStack::pushInvokeArgs(JSContext *cx, uintN argc, InvokeArgsGuard *iag)
 {
     uintN nvars = 2 + argc;
-    Value *firstUnused = ensureOnTop(cx, nvars, CAN_EXTEND, &iag->pushedSeg_);
+    Value *firstUnused = ensureOnTop(cx, REPORT_ERROR, nvars, CAN_EXTEND, &iag->pushedSeg_);
     if (!firstUnused)
         return false;
 
     ImplicitCast<CallArgs>(*iag) = CallArgsFromVp(argc, firstUnused);
 
     seg_->pushCall(*iag);
     JS_ASSERT(space().firstUnused() == iag->end());
     iag->setPushed(*this);
@@ -590,17 +622,17 @@ ContextStack::pushInvokeFrame(JSContext 
     JS_ASSERT(onTop());
     JS_ASSERT(space().firstUnused() == args.end());
 
     JSObject &callee = args.callee();
     JSFunction *fun = callee.getFunctionPrivate();
     JSScript *script = fun->script();
 
     StackFrame::Flags flags = ToFrameFlags(construct);
-    StackFrame *fp = getCallFrame(cx, args, fun, script, &flags, OOMCheck());
+    StackFrame *fp = getCallFrame(cx, REPORT_ERROR, args, fun, script, &flags);
     if (!fp)
         return false;
 
     fp->initCallFrame(cx, callee, fun, script, args.argc(), flags);
     ifg->regs_.prepareToRun(*fp, script);
 
     ifg->prevRegs_ = seg_->pushRegs(ifg->regs_);
     JS_ASSERT(space().firstUnused() == ifg->regs_.sp);
@@ -637,17 +669,17 @@ ContextStack::pushExecuteFrame(JSContext
         prev = evalInFrame;
         extend = CANT_EXTEND;
     } else {
         prev = maybefp();
         extend = CAN_EXTEND;
     }
 
     uintN nvars = 2 /* callee, this */ + VALUES_PER_STACK_FRAME + script->nslots;
-    Value *firstUnused = ensureOnTop(cx, nvars, extend, &efg->pushedSeg_);
+    Value *firstUnused = ensureOnTop(cx, REPORT_ERROR, nvars, extend, &efg->pushedSeg_);
     if (!firstUnused)
         return NULL;
 
     StackFrame *fp = reinterpret_cast<StackFrame *>(firstUnused + 2);
     fp->initExecuteFrame(script, prev, seg_->maybeRegs(), thisv, scopeChain, type);
     SetValueRangeToUndefined(fp->slots(), script->nfixed);
     efg->regs_.prepareToRun(*fp, script);
 
@@ -657,20 +689,21 @@ ContextStack::pushExecuteFrame(JSContext
 
     efg->prevRegs_ = seg_->pushRegs(efg->regs_);
     JS_ASSERT(space().firstUnused() == efg->regs_.sp);
     efg->setPushed(*this);
     return true;
 }
 
 bool
-ContextStack::pushDummyFrame(JSContext *cx, JSObject &scopeChain, DummyFrameGuard *dfg)
+ContextStack::pushDummyFrame(JSContext *cx, MaybeReportError report, JSObject &scopeChain,
+                             DummyFrameGuard *dfg)
 {
     uintN nvars = VALUES_PER_STACK_FRAME;
-    Value *firstUnused = ensureOnTop(cx, nvars, CAN_EXTEND, &dfg->pushedSeg_);
+    Value *firstUnused = ensureOnTop(cx, report, nvars, CAN_EXTEND, &dfg->pushedSeg_);
     if (!firstUnused)
         return NULL;
 
     StackFrame *fp = reinterpret_cast<StackFrame *>(firstUnused);
     fp->initDummyFrame(cx, scopeChain);
     dfg->regs_.initDummyFrame(*fp);
 
     dfg->prevRegs_ = seg_->pushRegs(dfg->regs_);
@@ -704,17 +737,17 @@ ContextStack::popFrame(const FrameGuard 
 bool
 ContextStack::pushGeneratorFrame(JSContext *cx, JSGenerator *gen, GeneratorFrameGuard *gfg)
 {
     StackFrame *genfp = gen->floatingFrame();
     Value *genvp = gen->floatingStack;
     uintN vplen = (Value *)genfp - genvp;
 
     uintN nvars = vplen + VALUES_PER_STACK_FRAME + genfp->numSlots();
-    Value *firstUnused = ensureOnTop(cx, nvars, CAN_EXTEND, &gfg->pushedSeg_);
+    Value *firstUnused = ensureOnTop(cx, REPORT_ERROR, nvars, CAN_EXTEND, &gfg->pushedSeg_);
     if (!firstUnused)
         return false;
 
     StackFrame *stackfp = reinterpret_cast<StackFrame *>(firstUnused + vplen);
     Value *stackvp = (Value *)stackfp - vplen;
 
     /* Save this for popGeneratorFrame. */
     gfg->gen_ = gen;
@@ -750,24 +783,40 @@ ContextStack::popGeneratorFrame(const Ge
 
     /* ~FrameGuard/popFrame will finish the popping. */
     JS_ASSERT(ImplicitCast<const FrameGuard>(gfg).pushed());
 }
 
 bool
 ContextStack::saveFrameChain()
 {
+    /*
+     * The StackSpace uses the context's current compartment to determine
+     * whether to allow access to the privileged end-of-stack buffer.
+     * However, we always want saveFrameChain to have access to this privileged
+     * buffer since it gets used to prepare calling trusted JS. To force this,
+     * we clear the current compartment (which is interpreted by ensureSpace as
+     * 'trusted') and either restore it on OOM or let resetCompartment()
+     * clobber it.
+     */
+    JSCompartment *original = cx_->compartment;
+    cx_->compartment = NULL;
+
     bool pushedSeg;
-    if (!ensureOnTop(cx_, 0, CANT_EXTEND, &pushedSeg))
+    if (!ensureOnTop(cx_, DONT_REPORT_ERROR, 0, CANT_EXTEND, &pushedSeg)) {
+        cx_->compartment = original;
+        js_ReportOverRecursed(cx_);
         return false;
+    }
+
     JS_ASSERT(pushedSeg);
     JS_ASSERT(!hasfp());
-    cx_->resetCompartment();
+    JS_ASSERT(onTop() && seg_->isEmpty());
 
-    JS_ASSERT(onTop() && seg_->isEmpty());
+    cx_->resetCompartment();
     return true;
 }
 
 void
 ContextStack::restoreFrameChain()
 {
     JS_ASSERT(onTop() && seg_->isEmpty());
 
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -382,17 +382,17 @@ class StackFrame
     /* Used for Invoke, Interpret, trace-jit LeaveTree, and method-jit stubs. */
     void initCallFrame(JSContext *cx, JSObject &callee, JSFunction *fun,
                        JSScript *script, uint32 nactual, StackFrame::Flags flags);
 
     /* Used for SessionInvoke. */
     void resetCallFrame(JSScript *script);
 
     /* Called by jit stubs and serve as a specification for jit-code. */
-    void initJitFrameCallerHalf(JSContext *cx, StackFrame::Flags flags, void *ncode);
+    void initJitFrameCallerHalf(StackFrame *prev, StackFrame::Flags flags, void *ncode);
     void initJitFrameEarlyPrologue(JSFunction *fun, uint32 nactual);
     bool initJitFrameLatePrologue(JSContext *cx, Value **limit);
 
     /* Used for eval. */
     void initExecuteFrame(JSScript *script, StackFrame *prev, FrameRegs *regs,
                           const Value &thisv, JSObject &scopeChain, ExecuteType type);
 
     /* Used when activating generators. */
@@ -1281,116 +1281,121 @@ class StackSegment
 
 static const size_t VALUES_PER_STACK_SEGMENT = sizeof(StackSegment) / sizeof(Value);
 JS_STATIC_ASSERT(sizeof(StackSegment) % sizeof(Value) == 0);
 
 /*****************************************************************************/
 
 class StackSpace
 {
+    StackSegment  *seg_;
     Value         *base_;
+    mutable Value *conservativeEnd_;
+#ifdef XP_WIN
     mutable Value *commitEnd_;
-    Value         *end_;
-    StackSegment  *seg_;
+#endif
+    Value         *defaultEnd_;
+    Value         *trustedEnd_;
 
+    void assertInvariants() const {
+        JS_ASSERT(base_ <= conservativeEnd_);
+#ifdef XP_WIN
+        JS_ASSERT(conservativeEnd_ <= commitEnd_);
+        JS_ASSERT(commitEnd_ <= trustedEnd_);
+#endif
+        JS_ASSERT(conservativeEnd_ <= defaultEnd_);
+        JS_ASSERT(defaultEnd_ <= trustedEnd_);
+    }
+
+    /* The total number of values/bytes reserved for the stack. */
     static const size_t CAPACITY_VALS  = 512 * 1024;
     static const size_t CAPACITY_BYTES = CAPACITY_VALS * sizeof(Value);
+
+    /* How much of the stack is initially committed. */
     static const size_t COMMIT_VALS    = 16 * 1024;
     static const size_t COMMIT_BYTES   = COMMIT_VALS * sizeof(Value);
 
+    /* How much space is reserved at the top of the stack for trusted JS. */
+    static const size_t BUFFER_VALS    = 16 * 1024;
+    static const size_t BUFFER_BYTES   = BUFFER_VALS * sizeof(Value);
+
     static void staticAsserts() {
         JS_STATIC_ASSERT(CAPACITY_VALS % COMMIT_VALS == 0);
     }
 
-#ifdef XP_WIN
-    JS_FRIEND_API(bool) bumpCommit(JSContext *maybecx, Value *from, ptrdiff_t nvals) const;
-#endif
-
     friend class AllFramesIter;
     friend class ContextStack;
     friend class StackFrame;
-    friend class OOMCheck;
-    inline bool ensureSpace(JSContext *maybecx, Value *from, ptrdiff_t nvals) const;
+
+    inline bool ensureSpace(JSContext *cx, MaybeReportError report,
+                            Value *from, ptrdiff_t nvals) const;
+    JS_FRIEND_API(bool) ensureSpaceSlow(JSContext *cx, MaybeReportError report,
+                                        Value *from, ptrdiff_t nvals) const;
     StackSegment &findContainingSegment(const StackFrame *target) const;
 
   public:
     StackSpace();
     bool init();
     ~StackSpace();
 
+    /*
+     * Maximum supported value of arguments.length. This bounds the maximum
+     * number of arguments that can be supplied to Function.prototype.apply.
+     * This value also bounds the number of elements parsed in an array
+     * initialiser.
+     *
+     * Since arguments are copied onto the stack, the stack size is the
+     * limiting factor for this constant. Use the max stack size (available to
+     * untrusted code) with an extra buffer so that, after such an apply, the
+     * callee can do a little work without OOMing.
+     */
+    static const uintN ARGS_LENGTH_MAX = CAPACITY_VALS - (2 * BUFFER_VALS);
+
     /* See stack layout comment above. */
     Value *firstUnused() const { return seg_ ? seg_->end() : base_; }
-    Value *endOfSpace() const { return end_; }
 
 #ifdef JS_TRACER
     /*
      * LeaveTree requires stack allocation to rebuild the stack. There is no
      * good way to handle an OOM for these allocations, so this function checks
      * that OOM cannot occur using the size of the TraceNativeStorage as a
      * conservative upper bound.
+     *
+     * Despite taking a 'cx', this function does not report an error if it
+     * returns 'false'.
      */
-    inline bool ensureEnoughSpaceToEnterTrace();
+    inline bool ensureEnoughSpaceToEnterTrace(JSContext *cx);
 #endif
 
     /*
      * Return a limit against which jit code can check for. This limit is not
      * necessarily the end of the stack since we lazily commit stack memory on
      * some platforms. Thus, when the stack limit is exceeded, the caller should
      * use tryBumpLimit to attempt to increase the stack limit by committing
      * more memory. If the stack is truly exhausted, tryBumpLimit will report an
      * error and return NULL.
      *
      * An invariant of the methodjit is that there is always space to push a
      * frame on top of the current frame's expression stack (which can be at
      * most script->nslots deep). getStackLimit ensures that the returned limit
      * does indeed have this required space and reports an error and returns
      * NULL if this reserve space cannot be allocated.
      */
-    inline Value *getStackLimit(JSContext *cx);
-    bool tryBumpLimit(JSContext *maybecx, Value *from, uintN nvals, Value **limit);
+    inline Value *getStackLimit(JSContext *cx, MaybeReportError report);
+    bool tryBumpLimit(JSContext *cx, Value *from, uintN nvals, Value **limit);
 
     /* Called during GC: mark segments, frames, and slots under firstUnused. */
     void mark(JSTracer *trc);
 
     /* We only report the committed size;  uncommitted size is uninteresting. */
     JS_FRIEND_API(size_t) committedSize();
 };
 
 /*****************************************************************************/
 
-/*
- * For pushInlineFrame, there are three different ways the caller may want to
- * check for stack overflow:
- *  - NoCheck: the caller has already ensured there is enough space
- *  - OOMCheck: perform normal checking against the end of the stack
- *  - LimitCheck: check against the given stack limit (see getStackLimit)
- */
-
-class NoCheck
-{
-  public:
-    bool operator()(JSContext *, StackSpace &, Value *, uintN) { return true; }
-};
-
-class OOMCheck
-{
-  public:
-    bool operator()(JSContext *cx, StackSpace &space, Value *from, uintN nvals);
-};
-
-class LimitCheck
-{
-    Value **limit;
-  public:
-    LimitCheck(Value **limit) : limit(limit) {}
-    bool operator()(JSContext *cx, StackSpace &space, Value *from, uintN nvals);
-};
-
-/*****************************************************************************/
-
 class ContextStack
 {
     StackSegment *seg_;
     StackSpace *space_;
     JSContext *cx_;
 
     /*
      * Return whether this ContextStack is at the top of the contiguous stack.
@@ -1406,23 +1411,22 @@ class ContextStack
     void assertSpaceInSync() const;
 #else
     void assertSpaceInSync() const {}
 #endif
 
     /* Implementation details of push* public interface. */
     StackSegment *pushSegment(JSContext *cx);
     enum MaybeExtend { CAN_EXTEND = true, CANT_EXTEND = false };
-    Value *ensureOnTop(JSContext *cx, uintN nvars, MaybeExtend extend, bool *pushedSeg);
+    Value *ensureOnTop(JSContext *cx, MaybeReportError report, uintN nvars,
+                       MaybeExtend extend, bool *pushedSeg);
 
-    /* Check = { OOMCheck, LimitCheck } */
-    template <class Check>
     inline StackFrame *
-    getCallFrame(JSContext *cx, const CallArgs &args, JSFunction *fun, JSScript *script,
-                 StackFrame::Flags *pflags, Check check) const;
+    getCallFrame(JSContext *cx, MaybeReportError report, const CallArgs &args,
+                 JSFunction *fun, JSScript *script, StackFrame::Flags *pflags) const;
 
     /* Make pop* functions private since only called by guard classes. */
     void popSegment();
     friend class InvokeArgsGuard;
     void popInvokeArgs(const InvokeArgsGuard &iag);
     friend class FrameGuard;
     void popFrame(const FrameGuard &fg);
     friend class GeneratorFrameGuard;
@@ -1490,43 +1494,46 @@ class ContextStack
      * Called by SendToGenerator to resume a yielded generator. In addition to
      * pushing a frame onto the VM stack, this function copies over the
      * floating frame stored in 'gen'. When 'gfg' is destroyed, the destructor
      * will copy the frame back to the floating frame.
      */
     bool pushGeneratorFrame(JSContext *cx, JSGenerator *gen, GeneratorFrameGuard *gfg);
 
     /* Pushes a "dummy" frame; should be removed one day. */
-    bool pushDummyFrame(JSContext *cx, JSObject &scopeChain, DummyFrameGuard *dfg);
+    bool pushDummyFrame(JSContext *cx, MaybeReportError report, JSObject &scopeChain,
+                        DummyFrameGuard *dfg);
 
     /*
      * An "inline frame" may only be pushed from within the top, active
      * segment. This is the case for calls made inside mjit code and Interpret.
-     * For the Check parameter, see OOMCheck et al above.
+     * The 'stackLimit' overload updates 'stackLimit' if it changes.
      */
-    template <class Check>
     bool pushInlineFrame(JSContext *cx, FrameRegs &regs, const CallArgs &args,
                          JSObject &callee, JSFunction *fun, JSScript *script,
-                         MaybeConstruct construct, Check check);
+                         MaybeConstruct construct);
+    bool pushInlineFrame(JSContext *cx, FrameRegs &regs, const CallArgs &args,
+                         JSObject &callee, JSFunction *fun, JSScript *script,
+                         MaybeConstruct construct, Value **stackLimit);
     void popInlineFrame(FrameRegs &regs);
 
     /* Pop a partially-pushed frame after hitting the limit before throwing. */
     void popFrameAfterOverflow();
 
     /*
      * Called by the methodjit for an arity mismatch. Arity mismatch can be
      * hot, so getFixupFrame avoids doing call setup performed by jit code when
      * FixupArity returns. In terms of work done:
      *
      *   getFixupFrame = pushInlineFrame -
      *                   (fp->initJitFrameLatePrologue + regs->prepareToRun)
      */
-    StackFrame *getFixupFrame(JSContext *cx, FrameRegs &regs, const CallArgs &args,
-                              JSFunction *fun, JSScript *script, void *ncode,
-                              MaybeConstruct construct, LimitCheck check);
+    StackFrame *getFixupFrame(JSContext *cx, MaybeReportError report,
+                              const CallArgs &args, JSFunction *fun, JSScript *script,
+                              void *ncode, MaybeConstruct construct, Value **stackLimit);
 
     bool saveFrameChain();
     void restoreFrameChain();
 
     /*
      * As an optimization, the interpreter/mjit can operate on a local
      * FrameRegs instance repoint the ContextStack to this local instance.
      */