Bug 1230710 - Reenable direct eval and arrow functions in derived class constructors. (r=jorendorff, r=shu)
authorEric Faust <efaustbmo@gmail.com>
Thu, 10 Dec 2015 12:50:35 -0800
changeset 310279 3c31d67c74d2d56c42b2b3ae913d21e0d8907c54
parent 310278 b6901bdf88e1eab8c45058dc1f1664184177984c
child 310280 02c69f4f896255189ce2f9f4e0d875e383bcfbd7
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff, shu
bugs1230710
milestone45.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 1230710 - Reenable direct eval and arrow functions in derived class constructors. (r=jorendorff, r=shu)
js/src/builtin/Eval.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/frontend/SharedContext.h
js/src/js.msg
js/src/jsobj.cpp
js/src/tests/ecma_6/Class/derivedConstructorArrowEvalBinding.js
js/src/tests/ecma_6/Class/derivedConstructorArrowEvalClosed.js
js/src/tests/ecma_6/Class/derivedConstructorArrowEvalEscape.js
js/src/tests/ecma_6/Class/derivedConstructorArrowEvalEscapeUninitialized.js
js/src/tests/ecma_6/Class/derivedConstructorArrowEvalGetThis.js
js/src/tests/ecma_6/Class/derivedConstructorArrowEvalNestedSuperCall.js
js/src/tests/ecma_6/Class/derivedConstructorArrowEvalSuperCall.js
js/src/tests/ecma_6/Class/derivedConstructorDisabled.js
js/src/tests/js1_8_5/reflect-parse/classes.js
js/src/vm/Debugger.cpp
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/ScopeObject.h
js/src/vm/Xdr.h
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -233,22 +233,16 @@ EvalKernel(JSContext* cx, const CallArgs
     AssertInnerizedScopeChain(cx, *scopeobj);
 
     Rooted<GlobalObject*> scopeObjGlobal(cx, &scopeobj->global());
     if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
         return false;
     }
 
-    if (evalType == DIRECT_EVAL && caller.script()->isDerivedClassConstructor()) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DISABLED_DERIVED_CLASS,
-                             "direct eval");
-        return false;
-    }
-
     // ES5 15.1.2.1 step 1.
     if (args.length() < 1) {
         args.rval().setUndefined();
         return true;
     }
     if (!args[0].isString()) {
         args.rval().set(args[0]);
         return true;
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -3470,20 +3470,44 @@ BytecodeEmitter::emitSetThis(ParseNode* 
     if (!emitTree(pn->pn_right))
         return false;
 
     if (!bindNameToSlot(name))
         return false;
 
     JSOp setOp = name->getOp();
 
+    // Handle the eval case. Only accept the strict variant, as eval in a
+    // derived class constructor must be strict.
+    if (setOp == JSOP_STRICTSETNAME) {
+        if (!emitAtomOp(name, JSOP_GETNAME))
+            return false;
+        if (!emit1(JSOP_CHECKTHISREINIT))
+            return false;
+        if (!emit1(JSOP_POP))
+            return false;
+
+        if (!emitAtomOp(name, JSOP_BINDNAME))
+            return false;
+        if (!emit1(JSOP_SWAP))
+            return false;
+
+        return emitAtomOp(name, setOp);
+    }
+
     JSOp getOp;
     switch (setOp) {
-      case JSOP_SETLOCAL:      getOp = JSOP_GETLOCAL;      break;
-      case JSOP_SETALIASEDVAR: getOp = JSOP_GETALIASEDVAR; break;
+      case JSOP_SETLOCAL:
+        getOp = JSOP_GETLOCAL;
+        setOp = JSOP_INITLEXICAL;
+        break;
+      case JSOP_SETALIASEDVAR:
+        getOp = JSOP_GETALIASEDVAR;
+        setOp = JSOP_INITALIASEDLEXICAL;
+        break;
       default: MOZ_CRASH("Unexpected op");
     }
 
     // First, get the original |this| and throw if we already initialized it.
     if (!emitVarOp(name, getOp))
         return false;
     if (!emit1(JSOP_CHECKTHISREINIT))
         return false;
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -120,18 +120,22 @@ MarkUsesAsHoistedLexical(ParseNode* pn)
 void
 SharedContext::computeAllowSyntax(JSObject* staticScope)
 {
     for (StaticScopeIter<CanGC> it(context, staticScope); !it.done(); it++) {
         if (it.type() == StaticScopeIter<CanGC>::Function && !it.fun().isArrow()) {
             // Any function supports new.target.
             allowNewTarget_ = true;
             allowSuperProperty_ = it.fun().allowSuperProperty();
-            if (it.maybeFunctionBox())
+            if (it.maybeFunctionBox()) {
                 superScopeAlreadyNeedsHomeObject_ = it.maybeFunctionBox()->needsHomeObject();
+                allowSuperCall_ = it.maybeFunctionBox()->isDerivedClassConstructor();
+            } else {
+                allowSuperCall_ = it.fun().isDerivedClassConstructor();
+            }
             break;
         }
     }
 }
 
 void
 SharedContext::computeThisBinding(JSObject* staticScope)
 {
@@ -7465,21 +7469,16 @@ Parser<ParseHandler>::assignExpr(InHandl
         tokenStream.seek(start);
         if (!abortIfSyntaxParser())
             return null();
 
         TokenKind ignored;
         if (!tokenStream.peekToken(&ignored, TokenStream::Operand))
             return null();
 
-        if (pc->sc->isFunctionBox() && pc->sc->asFunctionBox()->isDerivedClassConstructor()) {
-            report(ParseError, false, null(), JSMSG_DISABLED_DERIVED_CLASS, "arrow functions");
-            return null();
-        }
-
         Node arrowFunc = functionDef(inHandling, yieldHandling, nullptr, Arrow, NotGenerator);
         if (!arrowFunc)
             return null();
 
         if (isBlock) {
             // This arrow function could be a non-trailing member of a comma
             // expression or a semicolon terminating a full expression.  If so,
             // the next token is that comma/semicolon, gotten with None:
@@ -8885,17 +8884,17 @@ Parser<ParseHandler>::memberExpr(YieldHa
             nextMember = handler.newPropertyByValue(lhs, propExpr, pos().end);
             if (!nextMember)
                 return null();
         } else if ((allowCallSyntax && tt == TOK_LP) ||
                    tt == TOK_TEMPLATE_HEAD ||
                    tt == TOK_NO_SUBS_TEMPLATE)
         {
             if (handler.isSuperBase(lhs)) {
-                if (!pc->sc->isFunctionBox() || !pc->sc->asFunctionBox()->isDerivedClassConstructor()) {
+                if (!pc->sc->allowSuperCall()) {
                     report(ParseError, false, null(), JSMSG_BAD_SUPERCALL);
                     return null();
                 }
 
                 if (tt != TOK_LP) {
                     report(ParseError, false, null(), JSMSG_BAD_SUPER);
                     return null();
                 }
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -197,31 +197,33 @@ class SharedContext
     bool localStrict;
     bool extraWarnings;
 
   private:
     ThisBinding thisBinding_;
 
     bool allowNewTarget_;
     bool allowSuperProperty_;
+    bool allowSuperCall_;
     bool inWith_;
     bool needsThisTDZChecks_;
     bool superScopeAlreadyNeedsHomeObject_;
 
   public:
     SharedContext(ExclusiveContext* cx, Directives directives,
                   bool extraWarnings)
       : context(cx),
         anyCxFlags(),
         strictScript(directives.strict()),
         localStrict(false),
         extraWarnings(extraWarnings),
         thisBinding_(ThisBinding::Global),
         allowNewTarget_(false),
         allowSuperProperty_(false),
+        allowSuperCall_(false),
         inWith_(false),
         needsThisTDZChecks_(false),
         superScopeAlreadyNeedsHomeObject_(false)
     { }
 
     // The unfortunate reason that staticScope() is a virtual is because
     // GlobalSharedContext and FunctionBox have different lifetimes.
     // GlobalSharedContexts are stack allocated and thus may use RootedObject
@@ -239,16 +241,17 @@ class SharedContext
     bool isModuleBox() { return isObjectBox() && toObjectBox()->isModuleBox(); }
     inline ModuleBox* asModuleBox();
     bool isGlobalContext() { return !toObjectBox(); }
 
     ThisBinding thisBinding()          const { return thisBinding_; }
 
     bool allowNewTarget()              const { return allowNewTarget_; }
     bool allowSuperProperty()          const { return allowSuperProperty_; }
+    bool allowSuperCall()              const { return allowSuperCall_; }
     bool inWith()                      const { return inWith_; }
     bool needsThisTDZChecks()          const { return needsThisTDZChecks_; }
 
     void markSuperScopeNeedsHomeObject();
 
     bool hasExplicitUseStrict()        const { return anyCxFlags.hasExplicitUseStrict; }
     bool bindingsAccessedDynamically() const { return anyCxFlags.bindingsAccessedDynamically; }
     bool hasDebuggerStatement()        const { return anyCxFlags.hasDebuggerStatement; }
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -100,17 +100,16 @@ MSG_DEF(JSMSG_ALREADY_HAS_PRAGMA,      2
 MSG_DEF(JSMSG_NEXT_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "iterator.next() returned a non-object value")
 MSG_DEF(JSMSG_CANT_SET_PROTO,          0, JSEXN_TYPEERR, "can't set prototype of this object")
 MSG_DEF(JSMSG_CANT_SET_PROTO_OF,       1, JSEXN_TYPEERR, "can't set prototype of {0}")
 MSG_DEF(JSMSG_CANT_SET_PROTO_CYCLE,    0, JSEXN_TYPEERR, "can't set prototype: it would cause a prototype chain cycle")
 MSG_DEF(JSMSG_INVALID_ARG_TYPE,        3, JSEXN_TYPEERR, "Invalid type: {0} can't be a{1} {2}")
 MSG_DEF(JSMSG_TERMINATED,              1, JSEXN_ERR, "Script terminated by timeout at:\n{0}")
 MSG_DEF(JSMSG_PROTO_NOT_OBJORNULL,     1, JSEXN_TYPEERR, "{0}.prototype is not an object or null")
 MSG_DEF(JSMSG_CANT_CALL_CLASS_CONSTRUCTOR, 0, JSEXN_TYPEERR, "class constructors must be invoked with |new|")
-MSG_DEF(JSMSG_DISABLED_DERIVED_CLASS,  1, JSEXN_INTERNALERR, "{0} temporarily disallowed in derived class constructors")
 MSG_DEF(JSMSG_UNINITIALIZED_THIS,      1, JSEXN_REFERENCEERR, "|this| used uninitialized in {0} class constructor")
 MSG_DEF(JSMSG_BAD_DERIVED_RETURN,      1, JSEXN_TYPEERR, "derived class constructor returned invalid value {0}")
 
 // JSON
 MSG_DEF(JSMSG_JSON_BAD_PARSE,          3, JSEXN_SYNTAXERR, "JSON.parse: {0} at line {1} column {2} of the JSON data")
 MSG_DEF(JSMSG_JSON_CYCLIC_VALUE,       1, JSEXN_TYPEERR, "cyclic {0} value")
 
 // Runtime errors
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2226,21 +2226,22 @@ js::LookupNameUnqualified(JSContext* cx,
         if (!LookupProperty(cx, scope, id, &pobj, &shape))
             return false;
         if (shape)
             break;
     }
 
     // See note above RuntimeLexicalErrorObject.
     if (pobj == scope) {
-        if (IsUninitializedLexicalSlot(scope, shape)) {
+        if (name != cx->names().dotThis && IsUninitializedLexicalSlot(scope, shape)) {
             scope = RuntimeLexicalErrorObject::create(cx, scope, JSMSG_UNINITIALIZED_LEXICAL);
             if (!scope)
                 return false;
         } else if (scope->is<ScopeObject>() && !scope->is<DeclEnvObject>() && !shape->writable()) {
+            MOZ_ASSERT(name != cx->names().dotThis);
             scope = RuntimeLexicalErrorObject::create(cx, scope, JSMSG_BAD_CONST_ASSIGN);
             if (!scope)
                 return false;
         }
     }
 
     objp.set(scope);
     return true;
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorArrowEvalBinding.js
@@ -0,0 +1,19 @@
+// Make sure it doesn't matter when we make the arrow function
+var test = `
+
+new class extends class { } {
+    constructor() {
+        let arrow = () => this;
+        assertThrowsInstanceOf(arrow, ReferenceError);
+        super();
+        assertEq(arrow(), this);
+    }
+}();
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorArrowEvalClosed.js
@@ -0,0 +1,18 @@
+var test = `
+
+new class extends class { } {
+    constructor() {
+        let a1 = () => this;
+        let a2 = (() => super());
+        assertThrowsInstanceOf(a1, ReferenceError);
+        assertEq(a2(), a1());
+    }
+}();
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorArrowEvalEscape.js
@@ -0,0 +1,20 @@
+var test = `
+
+let arrow;
+
+class foo extends class { } {
+    constructor() {
+        arrow = () => this;
+        super();
+    }
+}
+
+assertEq(new foo(), arrow());
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorArrowEvalEscapeUninitialized.js
@@ -0,0 +1,45 @@
+var test = `
+
+let superArrow;
+let thisArrow;
+
+let thisStash;
+
+class base {
+    constructor() {
+        // We run this constructor twice as part of the double init check
+        if (!thisStash)
+            thisStash = {prop:45};
+        return thisStash;
+    }
+}
+
+class foo extends base {
+    constructor() {
+        superArrow = (()=>super());
+        thisArrow = ()=>this;
+    }
+}
+
+// Populate the arrow function saves. Since we never invoke super(), we throw
+assertThrowsInstanceOf(()=>new foo(), ReferenceError);
+
+// No |this| binding in the closure, yet
+assertThrowsInstanceOf(thisArrow, ReferenceError);
+
+// call super()
+superArrow();
+
+// Can't call it twice
+assertThrowsInstanceOf(superArrow, ReferenceError);
+
+// Oh look, |this| is populated, now.
+assertEq(thisArrow(), thisStash);
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorArrowEvalGetThis.js
@@ -0,0 +1,17 @@
+var test = `
+
+new class extends class { } {
+    constructor() {
+        super();
+        assertEq(this, (()=>this)());
+        assertEq(this, eval("this"));
+    }
+}();
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorArrowEvalNestedSuperCall.js
@@ -0,0 +1,41 @@
+var test = `
+
+new class extends class { } {
+    constructor() {
+        (()=>eval("super()"))();
+        assertEq(this, eval("this"));
+        assertEq(this, (()=>this)());
+    }
+}();
+
+new class extends class { } {
+    constructor() {
+        (()=>(()=>super())())();
+        assertEq(this, eval("this"));
+        assertEq(this, (()=>this)());
+    }
+}();
+
+new class extends class { } {
+    constructor() {
+        eval("(()=>super())()");
+        assertEq(this, eval("this"));
+        assertEq(this, (()=>this)());
+    }
+}();
+
+new class extends class { } {
+    constructor() {
+        eval("eval('super()')");
+        assertEq(this, eval("this"));
+        assertEq(this, (()=>this)());
+    }
+}();
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/derivedConstructorArrowEvalSuperCall.js
@@ -0,0 +1,25 @@
+var test = `
+
+new class extends class { } {
+    constructor() {
+        assertEq(eval("super(); this"), this);
+        assertEq(this, eval("this"));
+        assertEq(this, (()=>this)());
+    }
+}();
+
+new class extends class { } {
+    constructor() {
+        (()=>super())();
+        assertEq(this, eval("this"));
+        assertEq(this, (()=>this)());
+    }
+}();
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
deleted file mode 100644
--- a/js/src/tests/ecma_6/Class/derivedConstructorDisabled.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// |reftest| skip-if(!xulRuntime.shell)
-
-var test = `
-
-class base {
-    constructor() {
-        eval('');
-        (()=>0)();
-    }
-}
-
-class derived extends base {
-    constructor() {
-        eval('');
-    }
-}
-
-// Make sure eval and arrows are still valid in non-derived constructors.
-new base();
-
-
-// Eval throws in derived class constructors, in both class expressions and
-// statements.
-assertThrowsInstanceOf((() => new derived()), InternalError);
-assertThrowsInstanceOf((() => new class extends base { constructor() { eval('') } }()), InternalError);
-
-var g = newGlobal();
-var dbg = Debugger(g);
-dbg.onDebuggerStatement = function(frame) { assertThrowsInstanceOf(()=>frame.eval(''), InternalError); };
-
-g.eval("new class foo extends null { constructor() { debugger; return {}; } }()");
-`;
-
-if (classesEnabled())
-    eval(test);
-
-if (typeof reportCompare === 'function')
-    reportCompare(0,0,"OK");
--- a/js/src/tests/js1_8_5/reflect-parse/classes.js
+++ b/js/src/tests/js1_8_5/reflect-parse/classes.js
@@ -127,19 +127,16 @@ function testClasses() {
     assertClass("class NAME { constructor() { }; static [\"prototype\"]() { } }",
                 [ctorPlaceholder, emptyCPNMethod("prototype", true)]);
 
     /* Constructor */
     // Allow default constructors
     assertClass("class NAME { }", []);
     assertClass("class NAME extends null { }", [], lit(null));
 
-    // For now, disallow arrow functions in derived class constructors
-    assertClassError("class NAME extends null { constructor() { (() => 0); }", InternalError);
-
     // Derived class constructor must have curly brackets
     assertClassError("class NAME extends null {  constructor() 1 }", SyntaxError);
 
     // It is an error to have two methods named constructor, but not other
     // names, regardless if one is an accessor or a generator or static.
     assertClassError("class NAME { constructor() { } constructor(a) { } }", SyntaxError);
     let methods = [["method() { }", simpleMethod("method", "method", false)],
                    ["*method() { }", simpleMethod("method", "method", true)],
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -6719,23 +6719,16 @@ DebuggerGenericEval(JSContext* cx, const
                     EvalBindings evalWithBindings, HandleValue bindings, HandleValue options,
                     MutableHandleValue vp, Debugger* dbg, HandleObject scope,
                     ScriptFrameIter* iter)
 {
     /* Either we're specifying the frame, or a global. */
     MOZ_ASSERT_IF(iter, !scope);
     MOZ_ASSERT_IF(!iter, scope && IsGlobalLexicalScope(scope));
 
-    if (iter && iter->script()->isDerivedClassConstructor()) {
-        MOZ_ASSERT(iter->isFunctionFrame() && iter->calleeTemplate()->isClassConstructor());
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DISABLED_DERIVED_CLASS,
-                             "debugger eval");
-        return false;
-    }
-
     /* Check the first argument, the eval code string. */
     if (!code.isString()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
                              fullMethodName, "string", InformalValueTypeName(code));
         return false;
     }
     RootedLinearString linear(cx, code.toString()->ensureLinear(cx));
     if (!linear)
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -199,16 +199,20 @@ FetchName(JSContext* cx, HandleObject ob
             MOZ_ASSERT(shape->hasSlot());
             vp.set(obj2->as<NativeObject>().getSlot(shape->slot()));
         } else {
             if (!NativeGetExistingProperty(cx, normalized, obj2.as<NativeObject>(), shape, vp))
                 return false;
         }
     }
 
+    // We do our own explicit checking for |this|
+    if (name == cx->names().dotThis)
+        return true;
+
     // NAME operations are the slow paths already, so unconditionally check
     // for uninitialized lets.
     return CheckUninitializedLexical(cx, name, vp);
 }
 
 inline bool
 FetchNameNoGC(JSObject* pobj, Shape* shape, MutableHandleValue vp)
 {
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -3225,20 +3225,17 @@ CASE(JSOP_GETLOCAL)
         assertSameCompartmentDebugOnly(cx, REGS.sp[-1]);
 }
 END_CASE(JSOP_GETLOCAL)
 
 CASE(JSOP_SETLOCAL)
 {
     uint32_t i = GET_LOCALNO(REGS.pc);
 
-    // Derived class constructors store the TDZ Value in the .this slot
-    // before a super() call.
-    MOZ_ASSERT_IF(!script->isDerivedClassConstructor(),
-                  !IsUninitializedLexical(REGS.fp()->unaliasedLocal(i)));
+    MOZ_ASSERT(!IsUninitializedLexical(REGS.fp()->unaliasedLocal(i)));
 
     REGS.fp()->unaliasedLocal(i) = REGS.sp[-1];
 }
 END_CASE(JSOP_SETLOCAL)
 
 CASE(JSOP_DEFVAR)
 {
     /* ES5 10.5 step 8 (with subsequent errata). */
@@ -4838,19 +4835,16 @@ js::ReportRuntimeRedeclaration(JSContext
     }
 }
 
 bool
 js::ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame)
 {
     RootedFunction fun(cx, frame.callee());
 
-    MOZ_ASSERT(fun->isClassConstructor());
-    MOZ_ASSERT(fun->nonLazyScript()->isDerivedClassConstructor());
-
     const char* name = "anonymous";
     JSAutoByteString str;
     if (fun->atom()) {
         if (!AtomToPrintableString(cx, fun->atom(), &str))
             return false;
         name = str.ptr();
     }
 
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -948,17 +948,17 @@ class ClonedBlockObject : public BlockOb
 // (i.e. JSOP_SETNAME), we emit an accompanying, preceding JSOP_BINDNAME which
 // finds the right scope on which to set the name. Moreover, when the name on
 // the scope is an uninitialized lexical, we cannot throw eagerly, as the spec
 // demands that the error be thrown after evaluating the RHS of
 // assignments. Instead, this sentinel scope object is pushed on the stack.
 // Attempting to access anything on this scope throws the appropriate
 // ReferenceError.
 //
-// ES6 'const' bindings induce a runtime assignment when assigned to outside
+// ES6 'const' bindings induce a runtime error when assigned to outside
 // of initialization, regardless of strictness.
 class RuntimeLexicalErrorObject : public ScopeObject
 {
     static const unsigned ERROR_SLOT = 1;
 
   public:
     static const unsigned RESERVED_SLOTS = 2;
     static const Class class_;
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -24,21 +24,21 @@ namespace js {
  * versions.  If deserialization fails, the data should be invalidated if
  * possible.
  *
  * When you change this, run make_opcode_doc.py and copy the new output into
  * this wiki page:
  *
  *  https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
  */
-static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 328;
+static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 329;
 static const uint32_t XDR_BYTECODE_VERSION =
     uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
 
-static_assert(JSErr_Limit == 425,
+static_assert(JSErr_Limit == 424,
               "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
               "removed MSG_DEFs from js.msg, you should increment "
               "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "
               "expected JSErr_Limit value.");
 
 class XDRBuffer {
   public:
     explicit XDRBuffer(JSContext* cx)