From 3d819c1eaf499a990e2a839fb5375ed28e2e755d Mon Sep 17 00:00:00 2001 From: Roy Tam Date: Wed, 23 Jan 2019 11:35:44 +0800 Subject: [PATCH] import changes from rmottola/Arctic-Fox: - Bug 1101903 - Part 1: Convert SharedContext::strict to a method. (c419cb895) - Bug 1101903 - Part 2: Allow parsing and emitting strict mode code in smaller than script-sized units. (d00819026) - Bug 1124362 - Allow strict-reserved names to be method names. (6fd24146f) - Bug 1066227 - Part 1: Create a clean way to create lexical bindings at initalizer sites. (2305b65c6) - Bug 1066227 - Part 2: Rename objectLiteral() propertyList() in preparation for classes. (e53b9cf12) - Bug 1066227 - Part 3: Parser support for basic ES6 ClassStatements (Nightly Only). (5ff4cb3b9) - Bug 1066227 - Part 4: Reflect.parse support for ClassStatements (a67bae8a3) - Bug 1066227 - Tests. (ebe27243e) - Bug 1066229 - Part 1: Create a clean way to emit lexical initializers (2d4900e5b) - Bug 1066229 - Part 2: Factor EmitPropertyList() out of EmitObject(). (09b97b557) - Bug 1066229 - Part 3: Create JSOP_INITLOCKEDDPROP, which adds non-configurable non-writable non-enumerable properties. (80d4961b4) - Bug 1066229 - Part 4: Create JSOP_INITHIDDENPROP, which adds non-enumerable properties. (1c79190e4) - Bug 1066229 - Follow up: Enable |let| in ecma_6/Class/ in browser JS reftests. (12a117456) - Bug 1066229 - Tests. (8577d220a) - Bug 1066229 - Tests. (957f4fead) --- js/src/asmjs/AsmJSModule.cpp | 2 +- js/src/asmjs/AsmJSValidate.cpp | 2 +- js/src/frontend/BytecodeEmitter.cpp | 162 ++++-- js/src/frontend/FoldConstants.cpp | 10 + js/src/frontend/FullParseHandler.h | 28 +- js/src/frontend/ParseNode.cpp | 15 +- js/src/frontend/ParseNode.h | 96 +++- js/src/frontend/Parser.cpp | 513 ++++++++++++------ js/src/frontend/Parser.h | 32 +- js/src/frontend/SharedContext.h | 17 +- js/src/frontend/SyntaxParseHandler.h | 4 +- js/src/frontend/TokenKind.h | 1 + js/src/frontend/TokenStream.cpp | 8 +- js/src/frontend/TokenStream.h | 11 +- js/src/gc/Rooting.h | 1 + js/src/jit/BaselineCompiler.cpp | 14 +- js/src/jit/BaselineCompiler.h | 2 + js/src/jit/BaselineIC.cpp | 10 +- js/src/jit/CodeGenerator.cpp | 8 +- js/src/jit/IonBuilder.cpp | 6 +- js/src/jit/MIR.h | 16 +- js/src/jit/VMFunctions.cpp | 8 +- js/src/jit/VMFunctions.h | 12 +- js/src/js.msg | 6 + js/src/jsast.tbl | 2 + js/src/jsreflect.cpp | 110 ++++ js/src/jsscript.cpp | 2 +- js/src/jsversion.h | 5 + js/src/tests/browser.js | 4 + js/src/tests/ecma_6/Class/classPrototype.js | 27 + .../tests/ecma_6/Class/constructorCalled.js | 31 ++ js/src/tests/ecma_6/Class/innerBinding.js | 78 +++ js/src/tests/ecma_6/Class/methDefn.js | 24 + .../tests/ecma_6/Class/methodInstallation.js | 52 ++ js/src/tests/ecma_6/Class/methodOverwrites.js | 45 ++ js/src/tests/ecma_6/Class/outerBinding.js | 63 +++ js/src/tests/ecma_6/Class/shell.js | 12 + js/src/tests/ecma_6/Class/strictExecution.js | 20 + .../tests/js1_8_5/extensions/reflect-parse.js | 155 ++++++ js/src/vm/Interpreter.cpp | 27 +- js/src/vm/Interpreter.h | 3 + js/src/vm/Keywords.h | 2 +- js/src/vm/Opcodes.h | 25 +- js/src/vm/Xdr.h | 4 +- 44 files changed, 1389 insertions(+), 286 deletions(-) create mode 100644 js/src/tests/ecma_6/Class/classPrototype.js create mode 100644 js/src/tests/ecma_6/Class/constructorCalled.js create mode 100644 js/src/tests/ecma_6/Class/innerBinding.js create mode 100644 js/src/tests/ecma_6/Class/methodInstallation.js create mode 100644 js/src/tests/ecma_6/Class/methodOverwrites.js create mode 100644 js/src/tests/ecma_6/Class/outerBinding.js create mode 100644 js/src/tests/ecma_6/Class/strictExecution.js diff --git a/js/src/asmjs/AsmJSModule.cpp b/js/src/asmjs/AsmJSModule.cpp index 5396edf214..fb4dd69f16 100644 --- a/js/src/asmjs/AsmJSModule.cpp +++ b/js/src/asmjs/AsmJSModule.cpp @@ -2210,7 +2210,7 @@ js::LookupAsmJSModuleInCache(ExclusiveContext* cx, uint32_t srcStart = parser.pc->maybeFunction->pn_body->pn_pos.begin; uint32_t srcBodyStart = parser.tokenStream.currentToken().pos.end; - bool strict = parser.pc->sc->strict && !parser.pc->sc->hasExplicitUseStrict(); + bool strict = parser.pc->sc->strict() && !parser.pc->sc->hasExplicitUseStrict(); // usesSignalHandlers will be clobbered when deserializing ScopedJSDeletePtr module( diff --git a/js/src/asmjs/AsmJSValidate.cpp b/js/src/asmjs/AsmJSValidate.cpp index 7fc426992b..9ac01321d1 100644 --- a/js/src/asmjs/AsmJSValidate.cpp +++ b/js/src/asmjs/AsmJSValidate.cpp @@ -1549,7 +1549,7 @@ class MOZ_STACK_CLASS ModuleCompiler // "use strict" should be added to the source if we are in an implicit // strict context, see also comment above addUseStrict in // js::FunctionToString. - bool strict = parser_.pc->sc->strict && !parser_.pc->sc->hasExplicitUseStrict(); + bool strict = parser_.pc->sc->strict() && !parser_.pc->sc->hasExplicitUseStrict(); module_ = cx_->new_(parser_.ss, srcStart, srcBodyStart, strict, cx_->canUseSignalHandlers()); if (!module_) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 4c5a359da3..0cae65d56a 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -247,9 +247,9 @@ UpdateDepth(ExclusiveContext* cx, BytecodeEmitter* bce, ptrdiff_t target) static bool CheckStrictOrSloppy(BytecodeEmitter* bce, JSOp op) { - if (IsCheckStrictOp(op) && !bce->sc->strict) + if (IsCheckStrictOp(op) && !bce->sc->strict()) return false; - if (IsCheckSloppyOp(op) && bce->sc->strict) + if (IsCheckSloppyOp(op) && bce->sc->strict()) return false; return true; } @@ -1530,11 +1530,11 @@ StrictifySetNameOp(JSOp op, BytecodeEmitter* bce) { switch (op) { case JSOP_SETNAME: - if (bce->sc->strict) + if (bce->sc->strict()) op = JSOP_STRICTSETNAME; break; case JSOP_SETGNAME: - if (bce->sc->strict) + if (bce->sc->strict()) op = JSOP_STRICTSETGNAME; break; default:; @@ -1667,7 +1667,7 @@ TryConvertFreeName(BytecodeEmitter* bce, ParseNode* pn) // worth the trouble for doubly-nested eval code. So we conservatively // approximate. If the outer eval code is strict, then this eval code will // be: thus, don't optimize if we're compiling strict code inside an eval. - if (bce->insideEval && bce->sc->strict) + if (bce->insideEval && bce->sc->strict()) return false; JSOp op; @@ -2236,7 +2236,7 @@ BytecodeEmitter::reportStrictModeError(ParseNode* pn, unsigned errorNumber, ...) va_list args; va_start(args, errorNumber); - bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict, + bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict(), errorNumber, args); va_end(args); return result; @@ -2461,7 +2461,7 @@ EmitPropIncDec(ExclusiveContext* cx, ParseNode* pn, BytecodeEmitter* bce) return false; } - JSOp setOp = bce->sc->strict ? JSOP_STRICTSETPROP : JSOP_SETPROP; + JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; if (!EmitAtomOp(cx, pn->pn_kid, setOp, bce)) // N? N+1 return false; if (post && Emit1(cx, bce, JSOP_POP) < 0) // RESULT @@ -2584,7 +2584,7 @@ EmitElemIncDec(ExclusiveContext* cx, ParseNode* pn, BytecodeEmitter* bce) return false; } - JSOp setOp = bce->sc->strict ? JSOP_STRICTSETELEM : JSOP_SETELEM; + JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; if (!EmitElemOpBase(cx, bce, setOp)) // N? N+1 return false; if (post && Emit1(cx, bce, JSOP_POP) < 0) // RESULT @@ -3446,7 +3446,7 @@ EmitDestructuringLHS(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* targ return false; if (Emit1(cx, bce, JSOP_SWAP) < 0) return false; - JSOp setOp = bce->sc->strict ? JSOP_STRICTSETPROP : JSOP_SETPROP; + JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; if (!EmitAtomOp(cx, target, setOp, bce)) return false; break; @@ -3457,7 +3457,7 @@ EmitDestructuringLHS(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* targ // See the comment at `case PNK_DOT:` above. This case, // `[a[x]] = [b]`, is handled much the same way. The JSOP_SWAP // is emitted by EmitElemOperands. - JSOp setOp = bce->sc->strict ? JSOP_STRICTSETELEM : JSOP_SETELEM; + JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; if (!EmitElemOp(cx, target, setOp, bce)) return false; break; @@ -4218,7 +4218,7 @@ EmitAssignment(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* lhs, JSOp break; case PNK_DOT: { - JSOp setOp = bce->sc->strict ? JSOP_STRICTSETPROP : JSOP_SETPROP; + JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; if (!EmitIndexOp(cx, setOp, atomIndex, bce)) return false; break; @@ -4229,7 +4229,7 @@ EmitAssignment(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* lhs, JSOp break; case PNK_ELEM: { - JSOp setOp = bce->sc->strict ? JSOP_STRICTSETELEM : JSOP_SETELEM; + JSOp setOp = bce->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; if (Emit1(cx, bce, setOp) < 0) return false; break; @@ -5417,7 +5417,7 @@ EmitFunc(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) if (outersc->isFunctionBox() && outersc->asFunctionBox()->mightAliasLocals()) funbox->setMightAliasLocals(); // inherit mightAliasLocals from parent - MOZ_ASSERT_IF(outersc->strict, funbox->strict); + MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript); // Inherit most things (principals, version, etc) from the parent. Rooted parent(cx, bce->script); @@ -6043,7 +6043,7 @@ EmitStatement(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) // later in the script, because such statements are misleading. const char* directive = nullptr; if (atom == cx->names().useStrict) { - if (!bce->sc->strict) + if (!bce->sc->strictScript) directive = js_useStrict_str; } else if (atom == cx->names().useAsm) { if (bce->sc->isFunctionBox()) { @@ -6085,14 +6085,14 @@ EmitDelete(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) break; case PNK_DOT: { - JSOp delOp = bce->sc->strict ? JSOP_STRICTDELPROP : JSOP_DELPROP; + JSOp delOp = bce->sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP; if (!EmitPropOp(cx, pn2, delOp, bce)) return false; break; } case PNK_ELEM: { - JSOp delOp = bce->sc->strict ? JSOP_STRICTDELELEM : JSOP_DELELEM; + JSOp delOp = bce->sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM; if (!EmitElemOp(cx, pn2, delOp, bce)) return false; break; @@ -6567,36 +6567,11 @@ EmitConditionalExpression(ExclusiveContext* cx, BytecodeEmitter* bce, Conditiona return SetSrcNoteOffset(cx, bce, noteIndex, 0, jmp - beq); } -/* - * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See - * the comment on EmitSwitch. - */ -MOZ_NEVER_INLINE static bool -EmitObject(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) +static bool +EmitPropertyList(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, + MutableHandlePlainObject objp, PropListType type) { - if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext()) - return EmitSingletonInitialiser(cx, bce, pn); - - /* - * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing - * a new object and defining (in source order) each property on the object - * (or mutating the object's [[Prototype]], in the case of __proto__). - */ - ptrdiff_t offset = bce->offset(); - if (!EmitNewInit(cx, bce, JSProto_Object)) - return false; - - /* - * Try to construct the shape of the object as we go, so we can emit a - * JSOP_NEWOBJECT with the final shape instead. - */ - RootedPlainObject obj(cx); - if (bce->script->compileAndGo()) { - gc::AllocKind kind = GuessObjectGCKind(pn->pn_count); - obj = NewBuiltinClassInstance(cx, kind, TenuredObject); - if (!obj) - return false; - } + MOZ_ASSERT(type == ObjectLiteral); for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) { if (!UpdateSourceCoordNotes(cx, bce, propdef->pn_pos.begin)) @@ -6607,7 +6582,7 @@ EmitObject(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) if (propdef->isKind(PNK_MUTATEPROTO)) { if (!EmitTree(cx, bce, propdef->pn_kid)) return false; - obj = nullptr; + objp.set(nullptr); if (!Emit1(cx, bce, JSOP_MUTATEPROTO)) return false; continue; @@ -6647,10 +6622,10 @@ EmitObject(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) op == JSOP_INITPROP_SETTER); if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) - obj = nullptr; + objp.set(nullptr); if (isIndex) { - obj = nullptr; + objp.set(nullptr); switch (op) { case JSOP_INITPROP: op = JSOP_INITELEM; break; case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break; @@ -6666,23 +6641,59 @@ EmitObject(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) if (!bce->makeAtomIndex(key->pn_atom, &index)) return false; - if (obj) { - MOZ_ASSERT(!obj->inDictionaryMode()); + if (objp) { + MOZ_ASSERT(!objp->inDictionaryMode()); Rooted id(cx, AtomToId(key->pn_atom)); RootedValue undefinedValue(cx, UndefinedValue()); - if (!NativeDefineProperty(cx, obj, id, undefinedValue, nullptr, nullptr, + if (!NativeDefineProperty(cx, objp, id, undefinedValue, nullptr, nullptr, JSPROP_ENUMERATE)) { return false; } - if (obj->inDictionaryMode()) - obj = nullptr; + if (objp->inDictionaryMode()) + objp.set(nullptr); } if (!EmitIndex32(cx, op, index, bce)) return false; } } + return true; +} + +/* + * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See + * the comment on EmitSwitch. + */ +MOZ_NEVER_INLINE static bool +EmitObject(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) +{ + if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext()) + return EmitSingletonInitialiser(cx, bce, pn); + + /* + * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing + * a new object and defining (in source order) each property on the object + * (or mutating the object's [[Prototype]], in the case of __proto__). + */ + ptrdiff_t offset = bce->offset(); + if (!EmitNewInit(cx, bce, JSProto_Object)) + return false; + + /* + * Try to construct the shape of the object as we go, so we can emit a + * JSOP_NEWOBJECT with the final shape instead. + */ + RootedPlainObject obj(cx); + if (bce->script->compileAndGo()) { + gc::AllocKind kind = GuessObjectGCKind(pn->pn_count); + obj = NewBuiltinClassInstance(cx, kind, TenuredObject); + if (!obj) + return false; + } + + if (!EmitPropertyList(cx, bce, pn, &obj, ObjectLiteral)) + return false; if (obj) { /* @@ -6873,6 +6884,46 @@ EmitDefaults(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) return true; } + +static bool +EmitLexicalInitialization(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, + JSOp globalDefOp) +{ + /* + * This function is significantly more complicated than it needs to be. + * In fact, it shouldn't exist at all. This should all be a + * JSOP_INITLEXIAL. Unfortunately, toplevel lexicals are broken, and + * are emitted as vars :(. As such, we have to do these ministrations to + * to make sure that all works properly. + */ + MOZ_ASSERT(pn->isKind(PNK_NAME)); + + if (!BindNameToSlot(cx, bce, pn)) + return false; + + jsatomid atomIndex; + if (!MaybeEmitVarDecl(cx, bce, globalDefOp, pn, &atomIndex)) + return false; + + if (pn->getOp() != JSOP_INITLEXICAL) { + bool global = js_CodeSpec[pn->getOp()].format & JOF_GNAME; + if (!EmitIndex32(cx, global ? JSOP_BINDGNAME : JSOP_BINDNAME, atomIndex, bce)) + return false; + if (Emit1(cx, bce, JSOP_SWAP) < 0) + return false; + } + + if (!pn->pn_cookie.isFree()) { + if (!EmitVarOp(cx, pn, pn->getOp(), bce)) + return false; + } else { + if (!EmitIndexOp(cx, pn->getOp(), atomIndex, bce)) + return false; + } + + return true; +} + bool frontend::EmitTree(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) { @@ -7331,6 +7382,11 @@ frontend::EmitTree(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) MOZ_ASSERT(pn->getArity() == PN_NULLARY); break; + case PNK_CLASS: + // TODO: Implement emitter support for classes + bce->reportError(nullptr, JSMSG_CLASS_NOT_IMPLEMENTED); + return false; + default: MOZ_ASSERT(0); } diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 2eaf119e8d..60a96f3a1b 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -80,6 +80,13 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) *result = false; return true; + // Similarly to the lexical declarations above, classes cannot add hoisted + // declarations + case PNK_CLASS: + MOZ_ASSERT(node->isArity(PN_TERNARY)); + *result = false; + return true; + // ContainsHoistedDeclaration is only called on nested nodes, so any // instance of this can't be function statements at body level. In // SpiderMonkey, a binding induced by a function statement is added when @@ -407,6 +414,9 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_FOROF: case PNK_FORHEAD: case PNK_FRESHENBLOCK: + case PNK_CLASSMETHOD: + case PNK_CLASSMETHODLIST: + case PNK_CLASSNAMES: MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on " "some parent node without recurring to test this node"); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 7ffa6639f3..600cfeafbb 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -281,6 +281,16 @@ class FullParseHandler return literal; } + ParseNode *newClass(ParseNode *name, ParseNode *heritage, ParseNode *methodBlock) { + return new_(name, heritage, methodBlock); + } + ParseNode *newClassMethodList(uint32_t begin) { + return new_(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1)); + } + ParseNode *newClassNames(ParseNode *outer, ParseNode *inner, const TokenPos &pos) { + return new_(outer, inner, pos); + } + bool addPrototypeMutation(ParseNode* literal, uint32_t begin, ParseNode* expr) { // Object literals with mutated [[Prototype]] are non-constant so that // singleton objects will have Object.prototype as their [[Prototype]]. @@ -323,7 +333,7 @@ class FullParseHandler return true; } - bool addMethodDefinition(ParseNode* literal, ParseNode* key, ParseNode* fn, JSOp op) + bool addObjectMethodDefinition(ParseNode *literal, ParseNode *key, ParseNode *fn, JSOp op) { MOZ_ASSERT(literal->isArity(PN_LIST)); MOZ_ASSERT(key->isKind(PNK_NUMBER) || @@ -339,6 +349,22 @@ class FullParseHandler return true; } + bool addClassMethodDefinition(ParseNode *methodList, ParseNode *key, ParseNode *fn, JSOp op) + { + MOZ_ASSERT(methodList->isKind(PNK_CLASSMETHODLIST)); + MOZ_ASSERT(key->isKind(PNK_NUMBER) || + key->isKind(PNK_OBJECT_PROPERTY_NAME) || + key->isKind(PNK_STRING) || + key->isKind(PNK_COMPUTED_NAME)); + + // For now, there's no such thing as static methods. + ParseNode *classMethod = new_(key, fn, op, false); + if (!classMethod) + return false; + methodList->append(classMethod); + return true; + } + ParseNode* newYieldExpression(uint32_t begin, ParseNode* value, ParseNode* gen, JSOp op = JSOP_YIELD) { TokenPos pos(begin, value ? value->pn_pos.end : begin + 1); diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index fb2ee74d99..d0030b6914 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -269,6 +269,8 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_WHILE: case PNK_SWITCH: case PNK_LETBLOCK: + case PNK_CLASSNAMES: + case PNK_CLASSMETHOD: case PNK_FOR: { MOZ_ASSERT(pn->isArity(PN_BINARY)); stack->push(pn->pn_left); @@ -382,6 +384,16 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) return PushResult::Recyclable; } + // classes might have an optinal node for the heritage + case PNK_CLASS: { + MOZ_ASSERT(pn->isArity(PN_TERNARY)); + stack->push(pn->pn_kid1); + if (pn->pn_kid2) + stack->push(pn->pn_kid2); + stack->push(pn->pn_kid3); + return PushResult::Recyclable; + } + // if-statement nodes have condition and consequent children and a // possibly-null alternative. case PNK_IF: { @@ -463,6 +475,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_EXPORT_SPEC_LIST: case PNK_SEQ: case PNK_ARGSBODY: + case PNK_CLASSMETHODLIST: return PushListNodeChildren(pn, stack); // Array comprehension nodes are lists with a single child -- PNK_FOR for @@ -648,7 +661,7 @@ Parser::cloneParseTree(ParseNode* opn) case PN_CODE: NULLCHECK(pn->pn_funbox = newFunctionBox(pn, opn->pn_funbox->function(), pc, - Directives(/* strict = */ opn->pn_funbox->strict), + Directives(/* strict = */ opn->pn_funbox->strict()), opn->pn_funbox->generatorKind())); NULLCHECK(pn->pn_body = cloneParseTree(opn->pn_body)); pn->pn_cookie = opn->pn_cookie; diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index df682e342b..30c1e06e67 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -149,6 +149,10 @@ class UpvarCookie F(ARGSBODY) \ F(SPREAD) \ F(MUTATEPROTO) \ + F(CLASS) \ + F(CLASSMETHOD) \ + F(CLASSMETHODLIST) \ + F(CLASSNAMES) \ \ /* Unary operators. */ \ F(TYPEOF) \ @@ -573,6 +577,7 @@ class ParseNode union { unsigned iflags; /* JSITER_* flags for PNK_FOR node */ ObjectBox* objbox; /* Only for PN_BINARY_OBJ */ + bool isStatic; /* Only for PNK_CLASSMETHOD */ }; } binary; struct { /* one kid if unary */ @@ -1104,6 +1109,10 @@ struct LexicalScopeNode : public ParseNode pn_expr = blockNode; pn_blockid = blockNode->pn_blockid; } + + static bool test(const ParseNode &node) { + return node.isKind(PNK_LEXICALSCOPE); + } }; class LabeledStatement : public ParseNode @@ -1321,6 +1330,91 @@ struct CallSiteNode : public ListNode { } }; +struct ClassMethod : public BinaryNode { + /* + * Method defintions often keep a name and function body that overlap, + * so explicitly define the beginning and end here. + */ + ClassMethod(ParseNode *name, ParseNode *body, JSOp op, bool isStatic) + : BinaryNode(PNK_CLASSMETHOD, op, TokenPos(name->pn_pos.begin, body->pn_pos.end), name, body) + { + pn_u.binary.isStatic = isStatic; + } + + static bool test(const ParseNode &node) { + bool match = node.isKind(PNK_CLASSMETHOD); + MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); + return match; + } + + ParseNode &name() const { + return *pn_u.binary.left; + } + ParseNode &method() const { + return *pn_u.binary.right; + } + bool isStatic() const { + return pn_u.binary.isStatic; + } +}; + +struct ClassNames : public BinaryNode { + ClassNames(ParseNode *outerBinding, ParseNode *innerBinding, const TokenPos &pos) + : BinaryNode(PNK_CLASSNAMES, JSOP_NOP, pos, outerBinding, innerBinding) + { + MOZ_ASSERT(outerBinding->isKind(PNK_NAME)); + MOZ_ASSERT(innerBinding->isKind(PNK_NAME)); + MOZ_ASSERT(innerBinding->pn_atom == outerBinding->pn_atom); + } + + static bool test(const ParseNode &node) { + bool match = node.isKind(PNK_CLASSNAMES); + MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); + return match; + } + + /* + * Classes require two definitions: The first "outer" binding binds the + * class into the scope in which it was declared. the outer binding is a + * mutable lexial binding. The second "inner" binding binds the class by + * name inside a block in which the methods are evaulated. It is immutable, + * giving the methods access to the static members of the class even if + * the outer binding has been overwritten. + */ + ParseNode *outerBinding() const { + return pn_u.binary.left; + } + ParseNode *innerBinding() const { + return pn_u.binary.right; + } +}; + +struct ClassNode : public TernaryNode { + ClassNode(ParseNode *names, ParseNode *heritage, ParseNode *methodBlock) + : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodBlock) + { + MOZ_ASSERT(names->is()); + MOZ_ASSERT(methodBlock->is()); + } + + static bool test(const ParseNode &node) { + bool match = node.isKind(PNK_CLASS); + MOZ_ASSERT_IF(match, node.isArity(PN_TERNARY)); + return match; + } + + ClassNames *names() const { + return &pn_kid1->as(); + } + ParseNode *heritage() const { + return pn_kid2; + } + LexicalScopeNode *scope() const { + return &pn_kid3->as(); + } +}; + + #ifdef DEBUG void DumpParseTree(ParseNode* pn, int indent = 0); #endif @@ -1555,7 +1649,7 @@ enum ParseReportKind ParseStrictError }; -enum FunctionSyntaxKind { Expression, Statement, Arrow, Method }; +enum FunctionSyntaxKind { Expression, Statement, Arrow, Method, Lazy }; static inline ParseNode* FunctionArgsList(ParseNode* fn, unsigned* numFormals) diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index b061645e32..ec4cef36ca 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -757,7 +757,7 @@ Parser::reportBadReturn(Node pn, ParseReportKind kind, } else { errnum = anonerrnum; } - return report(kind, pc->sc->strict, pn, errnum, name.ptr()); + return report(kind, pc->sc->strict(), pn, errnum, name.ptr()); } /* @@ -780,7 +780,7 @@ Parser::checkStrictAssignment(Node lhs) if (!AtomToPrintableString(context, atom, &name)) return false; - if (!report(ParseStrictError, pc->sc->strict, lhs, JSMSG_BAD_STRICT_ASSIGN, name.ptr())) + if (!report(ParseStrictError, pc->sc->strict(), lhs, JSMSG_BAD_STRICT_ASSIGN, name.ptr())) return false; } return true; @@ -803,7 +803,7 @@ Parser::checkStrictBinding(PropertyName* name, Node pn) JSAutoByteString bytes; if (!AtomToPrintableString(context, name, &bytes)) return false; - return report(ParseStrictError, pc->sc->strict, pn, + return report(ParseStrictError, pc->sc->strict(), pn, JSMSG_BAD_BINDING, bytes.ptr()); } @@ -1479,7 +1479,7 @@ Parser::defineArg(Node funcpn, HandlePropertyName name, JSAutoByteString bytes; if (!AtomToPrintableString(context, name, &bytes)) return false; - if (!report(ParseStrictError, pc->sc->strict, pn, + if (!report(ParseStrictError, pc->sc->strict(), pn, JSMSG_DUPLICATE_FORMAL, bytes.ptr())) { return false; @@ -1866,7 +1866,7 @@ Parser::checkFunctionDefinition(HandlePropertyName funName, * statements (e.g., functions in an "if" or "while" block) are * dynamically bound when control flow reaches the statement. */ - MOZ_ASSERT(!pc->sc->strict); + MOZ_ASSERT(!pc->sc->strict()); MOZ_ASSERT(pn->pn_cookie.isFree()); if (pc->sc->isFunctionBox()) { FunctionBox* funbox = pc->sc->asFunctionBox(); @@ -2254,7 +2254,7 @@ Parser::finishFunctionDefinition(Node pn, FunctionBox* funbo for (size_t i = 0; i < numInnerFunctions; i++) innerFunctions[i].init(pc->innerFunctions[i]); - if (pc->sc->strict) + if (pc->sc->strict()) lazy->setStrict(); lazy->setGeneratorKind(funbox->generatorKind()); if (funbox->usesArguments && funbox->usesApply && funbox->usesThis) @@ -2446,7 +2446,7 @@ Parser::standaloneLazyFunction(HandleFunction fun, unsigned st if (!funpc.init(tokenStream)) return null(); - if (!functionArgsAndBodyGeneric(pn, fun, Normal, Statement)) { + if (!functionArgsAndBodyGeneric(pn, fun, Normal, Lazy)) { MOZ_ASSERT(directives == newDirectives); return null(); } @@ -2530,8 +2530,11 @@ Parser::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, Fu if (!body) return false; - if (fun->name() && !checkStrictBinding(fun->name(), pn)) + if (kind != Method && kind != Lazy && + fun->name() && !checkStrictBinding(fun->name(), pn)) + { return false; + } if (bodyType == StatementListBody) { bool matched; @@ -2549,7 +2552,7 @@ Parser::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, Fu if (tokenStream.hadError()) return false; funbox->bufEnd = pos().end; - if (kind == Statement && !MatchOrInsertSemicolon(tokenStream)) + if ((kind == Statement || kind == Lazy) && !MatchOrInsertSemicolon(tokenStream)) return false; } @@ -2562,7 +2565,7 @@ Parser::checkYieldNameValidity() { // In star generators and in JS >= 1.7, yield is a keyword. Otherwise in // strict mode, yield is a future reserved word. - if (pc->isStarGenerator() || versionNumber() >= JSVERSION_1_7 || pc->sc->strict) { + if (pc->isStarGenerator() || versionNumber() >= JSVERSION_1_7 || pc->sc->strict()) { report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); return false; } @@ -2601,7 +2604,7 @@ Parser::functionStmt() /* We forbid function statements in strict mode code. */ if (!pc->atBodyLevel() && pc->sc->needStrictChecks() && - !report(ParseStrictError, pc->sc->strict, null(), JSMSG_STRICT_FUNCTION_STATEMENT)) + !report(ParseStrictError, pc->sc->strict(), null(), JSMSG_STRICT_FUNCTION_STATEMENT)) return null(); return functionDef(name, Normal, Statement, generatorKind); @@ -2754,7 +2757,7 @@ Parser::maybeParseDirective(Node list, Node pn, bool* cont) // We're going to be in strict mode. Note that this scope explicitly // had "use strict"; pc->sc->setExplicitUseStrict(); - if (!pc->sc->strict) { + if (!pc->sc->strict()) { if (pc->sc->isFunctionBox()) { // Request that this function be reparsed as strict. pc->newDirectives->setStrict(); @@ -2767,7 +2770,7 @@ Parser::maybeParseDirective(Node list, Node pn, bool* cont) report(ParseError, false, null(), JSMSG_DEPRECATED_OCTAL); return false; } - pc->sc->strict = true; + pc->sc->strictScript = true; } } } else if (directive == context->names().useAsm) { @@ -3238,7 +3241,7 @@ Parser::makeSetCall(ParseNode* pn, unsigned msg) pn->isOp(JSOP_SPREADEVAL) || pn->isOp(JSOP_STRICTSPREADEVAL) || pn->isOp(JSOP_FUNCALL) || pn->isOp(JSOP_FUNAPPLY)); - if (!report(ParseStrictError, pc->sc->strict, pn, msg)) + if (!report(ParseStrictError, pc->sc->strict(), pn, msg)) return false; handler.markAsSetCall(pn); return true; @@ -3297,7 +3300,7 @@ Parser::noteNameUse(HandlePropertyName name, Node pn) template <> bool -Parser::bindDestructuringVar(BindData* data, ParseNode* pn) +Parser::bindInitialized(BindData *data, ParseNode *pn) { MOZ_ASSERT(pn->isKind(PNK_NAME)); @@ -3357,7 +3360,7 @@ Parser::checkDestructuringObject(BindData* d report(ParseError, false, expr, JSMSG_NO_VARIABLE_NAME); return false; } - ok = bindDestructuringVar(data, expr); + ok = bindInitialized(data, expr); } else { ok = checkAndMarkAsAssignmentLhs(expr, KeyedDestructuringAssignment); } @@ -3405,7 +3408,7 @@ Parser::checkDestructuringArray(BindData* da report(ParseError, false, target, JSMSG_NO_VARIABLE_NAME); return false; } - ok = bindDestructuringVar(data, target); + ok = bindInitialized(data, target); } else { ok = checkAndMarkAsAssignmentLhs(target, KeyedDestructuringAssignment); } @@ -3817,135 +3820,168 @@ Parser::variables(ParseNodeKind kind, bool* psimple, } template <> -ParseNode* +bool +Parser::checkAndPrepareLexical(bool isConst, const TokenPos &errorPos) +{ + /* + * This is a lexical declaration. We must be directly under a block per the + * proposed ES4 specs, but not an implicit block created due to + * 'for (let ...)'. If we pass this error test, make the enclosing + * StmtInfoPC be our scope. Further let declarations in this block will + * find this scope statement and use the same block object. + * + * If we are the first let declaration in this block (i.e., when the + * enclosing maybe-scope StmtInfoPC isn't yet a scope statement) then + * we also need to set pc->blockNode to be our PNK_LEXICALSCOPE. + */ + StmtInfoPC *stmt = pc->topStmt; + if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) { + reportWithOffset(ParseError, false, errorPos.begin, JSMSG_LEXICAL_DECL_NOT_IN_BLOCK, + isConst ? "const" : "lexical"); + return false; + } + + if (stmt && stmt->isBlockScope) { + MOZ_ASSERT(pc->staticScope == stmt->staticScope); + } else { + if (pc->atBodyLevel()) { + /* + * When bug 589199 is fixed, let variables will be stored in + * the slots of a new scope chain object, encountered just + * before the global object in the overall chain. This extra + * object is present in the scope chain for all code in that + * global, including self-hosted code. But self-hosted code + * must be usable against *any* global object, including ones + * with other let variables -- variables possibly placed in + * conflicting slots. Forbid top-level let declarations to + * prevent such conflicts from ever occurring. + */ + bool isGlobal = !pc->sc->isFunctionBox() && stmt == pc->topScopeStmt; + if (options().selfHostingMode && isGlobal) { + report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL, + isConst ? "'const'" : "'let'"); + return false; + } + return true; + } + + /* + * Some obvious assertions here, but they may help clarify the + * situation. This stmt is not yet a scope, so it must not be a + * catch block (catch is a lexical scope by definition). + */ + MOZ_ASSERT(!stmt->isBlockScope); + MOZ_ASSERT(stmt != pc->topScopeStmt); + MOZ_ASSERT(stmt->type == STMT_BLOCK || + stmt->type == STMT_SWITCH || + stmt->type == STMT_TRY || + stmt->type == STMT_FINALLY); + MOZ_ASSERT(!stmt->downScope); + + /* Convert the block statement into a scope statement. */ + StaticBlockObject *blockObj = StaticBlockObject::create(context); + if (!blockObj) + return false; + + ObjectBox *blockbox = newObjectBox(blockObj); + if (!blockbox) + return false; + + /* + * Insert stmt on the pc->topScopeStmt/stmtInfo.downScope linked + * list stack, if it isn't already there. If it is there, but it + * lacks the SIF_SCOPE flag, it must be a try, catch, or finally + * block. + */ + stmt->isBlockScope = stmt->isNestedScope = true; + stmt->downScope = pc->topScopeStmt; + pc->topScopeStmt = stmt; + + blockObj->initEnclosingNestedScopeFromParser(pc->staticScope); + pc->staticScope = blockObj; + stmt->staticScope = blockObj; + +#ifdef DEBUG + ParseNode *tmp = pc->blockNode; + MOZ_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE)); +#endif + + /* Create a new lexical scope node for these statements. */ + ParseNode *pn1 = handler.new_(blockbox, pc->blockNode); + if (!pn1) + return false;; + pc->blockNode = pn1; + } + return true; +} + +static StaticBlockObject * +CurrentLexicalStaticBlock(ParseContext *pc) +{ + return pc->atBodyLevel() ? nullptr : + &pc->staticScope->as(); +} + +template <> +ParseNode * +Parser::makeInitializedLexicalBinding(HandlePropertyName name, bool isConst, + const TokenPos &pos) +{ + // Handle the silliness of global and body level lexical decls. + BindData data(context); + if (pc->atGlobalLevel()) { + data.initVarOrGlobalConst(isConst ? JSOP_DEFCONST : JSOP_DEFVAR); + } else { + if (!checkAndPrepareLexical(isConst, pos)) + return null(); + data.initLexical(HoistVars, CurrentLexicalStaticBlock(pc), JSMSG_TOO_MANY_LOCALS, isConst); + } + ParseNode *dn = newBindingNode(name, pc->atGlobalLevel()); + if (!dn) + return null(); + handler.setPosition(dn, pos); + + if (!bindInitialized(&data, dn)) + return null(); + + return dn; +} + +template <> +ParseNode * Parser::lexicalDeclaration(bool isConst) { handler.disableSyntaxParser(); - ParseNode* pn; + if (!checkAndPrepareLexical(isConst, pos())) + return null(); - do { - /* - * This is a let declaration. We must be directly under a block per the - * proposed ES4 specs, but not an implicit block created due to - * 'for (let ...)'. If we pass this error test, make the enclosing - * StmtInfoPC be our scope. Further let declarations in this block will - * find this scope statement and use the same block object. - * - * If we are the first let declaration in this block (i.e., when the - * enclosing maybe-scope StmtInfoPC isn't yet a scope statement) then - * we also need to set pc->blockNode to be our PNK_LEXICALSCOPE. - */ - StmtInfoPC* stmt = pc->topStmt; - if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) { - report(ParseError, false, null(), JSMSG_LEXICAL_DECL_NOT_IN_BLOCK, - isConst ? "const" : "let"); - return null(); - } - - if (stmt && stmt->isBlockScope) { - MOZ_ASSERT(pc->staticScope == stmt->staticScope); - } else { - if (pc->atBodyLevel()) { - /* - * When bug 589199 is fixed, let variables will be stored in - * the slots of a new scope chain object, encountered just - * before the global object in the overall chain. This extra - * object is present in the scope chain for all code in that - * global, including self-hosted code. But self-hosted code - * must be usable against *any* global object, including ones - * with other let variables -- variables possibly placed in - * conflicting slots. Forbid top-level let declarations to - * prevent such conflicts from ever occurring. - */ - bool isGlobal = !pc->sc->isFunctionBox() && stmt == pc->topScopeStmt; - if (options().selfHostingMode && isGlobal) { - report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL, - isConst ? "'const'" : "'let'"); - return null(); - } - - /* - * Parse body-level lets without a new block object. ES6 specs - * that an execution environment's initial lexical environment - * is the VariableEnvironment, i.e., body-level lets are in - * the same environment record as vars. - * - * However, they cannot be parsed exactly as vars, as ES6 - * requires that uninitialized lets throw ReferenceError on use. - * - * See 8.1.1.1.6 and the note in 13.2.1. - * - * FIXME global-level lets are still considered vars until - * other bugs are fixed. - */ - ParseNodeKind kind = PNK_LET; - if (isGlobal) - kind = isConst ? PNK_GLOBALCONST : PNK_VAR; - else if (isConst) - kind = PNK_CONST; - pn = variables(kind); - if (!pn) - return null(); - pn->pn_xflags |= PNX_POPVAR; - break; - } - - /* - * Some obvious assertions here, but they may help clarify the - * situation. This stmt is not yet a scope, so it must not be a - * catch block (catch is a lexical scope by definition). - */ - MOZ_ASSERT(!stmt->isBlockScope); - MOZ_ASSERT(stmt != pc->topScopeStmt); - MOZ_ASSERT(stmt->type == STMT_BLOCK || - stmt->type == STMT_SWITCH || - stmt->type == STMT_TRY || - stmt->type == STMT_FINALLY); - MOZ_ASSERT(!stmt->downScope); - - /* Convert the block statement into a scope statement. */ - StaticBlockObject* blockObj = StaticBlockObject::create(context); - if (!blockObj) - return null(); - - ObjectBox* blockbox = newObjectBox(blockObj); - if (!blockbox) - return null(); - - /* - * Insert stmt on the pc->topScopeStmt/stmtInfo.downScope linked - * list stack, if it isn't already there. If it is there, but it - * lacks the SIF_SCOPE flag, it must be a try, catch, or finally - * block. - */ - stmt->isBlockScope = stmt->isNestedScope = true; - stmt->downScope = pc->topScopeStmt; - pc->topScopeStmt = stmt; - - blockObj->initEnclosingNestedScopeFromParser(pc->staticScope); - pc->staticScope = blockObj; - stmt->staticScope = blockObj; - -#ifdef DEBUG - ParseNode* tmp = pc->blockNode; - MOZ_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE)); -#endif - - /* Create a new lexical scope node for these statements. */ - ParseNode* pn1 = handler.new_(blockbox, pc->blockNode); - if (!pn1) - return null(); - pc->blockNode = pn1; - } - - pn = variables(isConst ? PNK_CONST : PNK_LET, nullptr, - &pc->staticScope->as(), HoistVars); - if (!pn) - return null(); - pn->pn_xflags = PNX_POPVAR; - } while (0); + /* + * Parse body-level lets without a new block object. ES6 specs + * that an execution environment's initial lexical environment + * is the VariableEnvironment, i.e., body-level lets are in + * the same environment record as vars. + * + * However, they cannot be parsed exactly as vars, as ES6 + * requires that uninitialized lets throw ReferenceError on use. + * + * See 8.1.1.1.6 and the note in 13.2.1. + * + * FIXME global-level lets are still considered vars until + * other bugs are fixed. + */ + ParseNodeKind kind = PNK_LET; + if (pc->atGlobalLevel()) + kind = isConst ? PNK_GLOBALCONST : PNK_VAR; + else if (isConst) + kind = PNK_CONST; + ParseNode *pn = variables(kind, nullptr, + CurrentLexicalStaticBlock(pc), + HoistVars); + if (!pn) + return null(); + pn->pn_xflags = PNX_POPVAR; return MatchOrInsertSemicolon(tokenStream) ? pn : nullptr; } @@ -4436,7 +4472,7 @@ Parser::forStatement() iflags = JSITER_FOREACH; isForEach = true; if (versionNumber() < JSVERSION_LATEST) { - if (!report(ParseWarning, pc->sc->strict, null(), JSMSG_DEPRECATED_FOR_EACH)) + if (!report(ParseWarning, pc->sc->strict(), null(), JSMSG_DEPRECATED_FOR_EACH)) return null(); } } @@ -5356,7 +5392,7 @@ Parser::withStatement() // construct that is forbidden in strict mode code, but doesn't even merit a // warning under JSOPTION_EXTRA_WARNINGS. See // https://bugzilla.mozilla.org/show_bug.cgi?id=514576#c1. - if (pc->sc->strict && !report(ParseStrictError, true, null(), JSMSG_STRICT_CODE_WITH)) + if (pc->sc->strict() && !report(ParseStrictError, true, null(), JSMSG_STRICT_CODE_WITH)) return null(); MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH); @@ -5659,6 +5695,80 @@ Parser::debuggerStatement() return handler.newDebuggerStatement(p); } +template <> +ParseNode * +Parser::classStatement() +{ + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS)); + + TokenKind tt; + if (!tokenStream.getToken(&tt)) + return null(); + + RootedPropertyName name(context); + if (tt == TOK_NAME) { + name = tokenStream.currentName(); + } else if (tt == TOK_YIELD) { + if (!checkYieldNameValidity()) + return null(); + name = tokenStream.currentName(); + } else { + // Class statements must have a bound name + report(ParseError, false, null(), JSMSG_UNNAMED_CLASS_STMT); + return null(); + } + + if (name == context->names().let) { + report(ParseError, false, null(), JSMSG_LET_CLASS_BINDING); + return null(); + } + + // Because the binding definitions keep track of their blockId, we need to + // create at least the inner binding later. Keep track of the name's position + // in order to provide it for the nodes created later. + TokenPos namePos = pos(); + + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CLASS); + + bool savedStrictness = setLocalStrictMode(true); + + StmtInfoPC classStmt(context); + ParseNode *classBlock = pushLexicalScope(&classStmt); + if (!classBlock) + return null(); + + ParseNode *classMethods = propertyList(ClassBody); + if (!classMethods) + return null(); + handler.setLexicalScopeBody(classBlock, classMethods); + + ParseNode *innerBinding = makeInitializedLexicalBinding(name, true, namePos); + if (!innerBinding) + return null(); + + PopStatementPC(tokenStream, pc); + + ParseNode *outerBinding = makeInitializedLexicalBinding(name, false, namePos); + if (!outerBinding) + return null(); + + ParseNode *nameNode = handler.newClassNames(outerBinding, innerBinding, namePos); + if (!nameNode) + return null(); + + MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness)); + + return handler.newClass(nameNode, null(), classBlock); +} + +template <> +SyntaxParseHandler::Node +Parser::classStatement() +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + template typename ParseHandler::Node Parser::statement(bool canHaveDirectives) @@ -5729,6 +5839,11 @@ Parser::statement(bool canHaveDirectives) return functionStmt(); case TOK_DEBUGGER: return debuggerStatement(); + case TOK_CLASS: + if (!abortIfSyntaxParser()) + return null(); + return classStatement(); + /* TOK_CATCH and TOK_FINALLY are both handled in the TOK_TRY case */ case TOK_CATCH: @@ -6285,7 +6400,7 @@ Parser::unaryExpr(InvokedPrediction invoked) // Per spec, deleting any unary expression is valid -- it simply // returns true -- except for one case that is illegal in strict mode. if (handler.isName(expr)) { - if (!report(ParseStrictError, pc->sc->strict, expr, JSMSG_DEPRECATED_DELETE_OPERAND)) + if (!report(ParseStrictError, pc->sc->strict(), expr, JSMSG_DEPRECATED_DELETE_OPERAND)) return null(); pc->sc->setBindingsAccessedDynamically(); } @@ -6690,7 +6805,7 @@ Parser::legacyComprehensionTail(ParseNode* bodyExpr, unsigned if (matched) { pn2->pn_iflags |= JSITER_FOREACH; if (versionNumber() < JSVERSION_LATEST) { - if (!report(ParseWarning, pc->sc->strict, pn2, JSMSG_DEPRECATED_FOR_EACH)) + if (!report(ParseWarning, pc->sc->strict(), pn2, JSMSG_DEPRECATED_FOR_EACH)) return null(); } } @@ -6946,7 +7061,7 @@ Parser::generatorComprehensionLambda(GeneratorKind comprehensionKi return null(); // Create box for fun->object early to root it. - Directives directives(/* strict = */ outerpc->sc->strict); + Directives directives(/* strict = */ outerpc->sc->strict()); FunctionBox* genFunbox = newFunctionBox(genfn, fun, outerpc, directives, comprehensionKind); if (!genFunbox) return null(); @@ -7466,7 +7581,7 @@ Parser::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred if (JSAtom* atom = handler.isName(lhs)) { if (tt == TOK_LP && atom == context->names().eval) { /* Select JSOP_EVAL and flag pc as heavyweight. */ - op = pc->sc->strict ? JSOP_STRICTEVAL : JSOP_EVAL; + op = pc->sc->strict() ? JSOP_STRICTEVAL : JSOP_EVAL; pc->sc->setBindingsAccessedDynamically(); pc->sc->setHasDirectEval(); @@ -7474,7 +7589,7 @@ Parser::memberExpr(TokenKind tt, bool allowCallSyntax, InvokedPred * In non-strict mode code, direct calls to eval can add * variables to the call object. */ - if (pc->sc->isFunctionBox() && !pc->sc->strict) + if (pc->sc->isFunctionBox() && !pc->sc->strict()) pc->sc->asFunctionBox()->setHasExtensibleScope(); } } else if (JSAtom* atom = handler.isGetProp(lhs)) { @@ -7761,15 +7876,27 @@ Parser::computedPropertyName(Node literal) template typename ParseHandler::Node -Parser::objectLiteral() +Parser::newPropertyListNode(PropListType type) +{ + if (type == ClassBody) + return handler.newClassMethodList(pos().begin); + + MOZ_ASSERT(type == ObjectLiteral); + return handler.newObjectLiteral(pos().begin); +} + +template +typename ParseHandler::Node +Parser::propertyList(PropListType type) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); - Node literal = handler.newObjectLiteral(pos().begin); - if (!literal) + Node propList = newPropertyListNode(type); + if (!propList) return null(); bool seenPrototypeMutation = false; + bool seenConstructor = false; RootedAtom atom(context); for (;;) { TokenKind ltok; @@ -7778,6 +7905,9 @@ Parser::objectLiteral() if (ltok == TOK_RC) break; + if (type == ClassBody && ltok == TOK_SEMI) + continue; + bool isGenerator = false; if (ltok == TOK_MUL) { isGenerator = true; @@ -7800,7 +7930,7 @@ Parser::objectLiteral() break; case TOK_LB: { - propname = computedPropertyName(literal); + propname = computedPropertyName(propList); if (!propname) return null(); break; @@ -7856,7 +7986,7 @@ Parser::objectLiteral() if (!propname) return null(); } else if (tt == TOK_LB) { - propname = computedPropertyName(literal); + propname = computedPropertyName(propList); if (!propname) return null(); } else { @@ -7893,12 +8023,30 @@ Parser::objectLiteral() return null(); } + if (type == ClassBody) { + if (atom == context->names().constructor) { + if (isGenerator || op != JSOP_INITPROP) { + report(ParseError, false, propname, JSMSG_BAD_METHOD_DEF); + return null(); + } + if (seenConstructor) { + report(ParseError, false, propname, JSMSG_DUPLICATE_PROPERTY, "constructor"); + return null(); + } + seenConstructor = true; + } + } + if (op == JSOP_INITPROP) { TokenKind tt; if (!tokenStream.getToken(&tt)) return null(); if (tt == TOK_COLON) { + if (type == ClassBody) { + report(ParseError, false, null(), JSMSG_BAD_METHOD_DEF); + return null(); + } if (isGenerator) { report(ParseError, false, null(), JSMSG_BAD_PROP_ID); return null(); @@ -7923,13 +8071,13 @@ Parser::objectLiteral() // method/generator definitions, computed property name // versions of all of these, and shorthands do not. uint32_t begin = handler.getPosition(propname).begin; - if (!handler.addPrototypeMutation(literal, begin, propexpr)) + if (!handler.addPrototypeMutation(propList, begin, propexpr)) return null(); } else { if (!handler.isConstant(propexpr)) - handler.setListFlag(literal, PNX_NONCONST); + handler.setListFlag(propList, PNX_NONCONST); - if (!handler.addPropertyDefinition(literal, propname, propexpr)) + if (!handler.addPropertyDefinition(propList, propname, propexpr)) return null(); } } else if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC)) { @@ -7937,6 +8085,10 @@ Parser::objectLiteral() * Support, e.g., |var {x, y} = o| as destructuring shorthand * for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8. */ + if (type == ClassBody) { + report(ParseError, false, null(), JSMSG_BAD_METHOD_DEF); + return null(); + } if (isGenerator) { report(ParseError, false, null(), JSMSG_BAD_PROP_ID); return null(); @@ -7950,11 +8102,11 @@ Parser::objectLiteral() if (!nameExpr) return null(); - if (!handler.addShorthand(literal, propname, nameExpr)) + if (!handler.addShorthand(propList, propname, nameExpr)) return null(); } else if (tt == TOK_LP) { tokenStream.ungetToken(); - if (!methodDefinition(literal, propname, Normal, Method, + if (!methodDefinition(type, propList, propname, Normal, Method, isGenerator ? StarGenerator : NotGenerator, op)) { return null(); } @@ -7964,32 +8116,40 @@ Parser::objectLiteral() } } else { /* NB: Getter function in { get x(){} } is unnamed. */ - if (!methodDefinition(literal, propname, op == JSOP_INITPROP_GETTER ? Getter : Setter, + if (!methodDefinition(type, propList, propname, op == JSOP_INITPROP_GETTER ? Getter : Setter, Expression, NotGenerator, op)) { return null(); } } - TokenKind tt; - if (!tokenStream.getToken(&tt)) - return null(); - if (tt == TOK_RC) - break; - if (tt != TOK_COMMA) { - report(ParseError, false, null(), JSMSG_CURLY_AFTER_LIST); - return null(); + if (type == ObjectLiteral) { + TokenKind tt; + if (!tokenStream.getToken(&tt)) + return null(); + if (tt == TOK_RC) + break; + if (tt != TOK_COMMA) { + report(ParseError, false, null(), JSMSG_CURLY_AFTER_LIST); + return null(); + } } } - handler.setEndPosition(literal, pos().end); - return literal; + // Default constructors not yet implemented. See bug 1105463 + if (type == ClassBody && !seenConstructor) { + report(ParseError, false, null(), JSMSG_NO_CLASS_CONSTRUCTOR); + return null(); + } + + handler.setEndPosition(propList, pos().end); + return propList; } template bool -Parser::methodDefinition(Node literal, Node propname, FunctionType type, - FunctionSyntaxKind kind, GeneratorKind generatorKind, - JSOp op) +Parser::methodDefinition(PropListType listType, Node propList, Node propname, + FunctionType type, FunctionSyntaxKind kind, + GeneratorKind generatorKind, JSOp op) { RootedPropertyName funName(context); if (kind == Method && tokenStream.isCurrentTokenType(TOK_NAME)) @@ -8000,9 +8160,12 @@ Parser::methodDefinition(Node literal, Node propname, FunctionType Node fn = functionDef(funName, type, kind, generatorKind); if (!fn) return false; - if (!handler.addMethodDefinition(literal, propname, fn, op)) - return false; - return true; + + if (listType == ClassBody) + return handler.addClassMethodDefinition(propList, propname, fn, op); + + MOZ_ASSERT(listType == ObjectLiteral); + return handler.addObjectMethodDefinition(propList, propname, fn, op); } template @@ -8020,7 +8183,7 @@ Parser::primaryExpr(TokenKind tt, InvokedPrediction invoked) return arrayInitializer(); case TOK_LC: - return objectLiteral(); + return propertyList(ObjectLiteral); case TOK_LP: { TokenKind next; diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 842e6f3c59..bfc976887d 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -301,6 +301,7 @@ struct ParseContext : public GenericParseContext // if (cond) { function f3() { if (cond) { function f4() { } } } } // bool atBodyLevel() { return !topStmt; } + bool atGlobalLevel() { return atBodyLevel() && !sc->isFunctionBox() && (topStmt == topScopeStmt); } // True if this is the ParseContext for the body of a function created by // the Function constructor. @@ -316,7 +317,7 @@ struct ParseContext : public GenericParseContext template inline Directives::Directives(ParseContext* parent) - : strict_(parent->sc->strict), + : strict_(parent->sc->strict()), asmJS_(parent->useAsmOrInsideUseAsm()) {} @@ -327,6 +328,7 @@ class CompExprTransplanter; enum VarContext { HoistVars, DontHoistVars }; enum FunctionType { Getter, Setter, Normal }; +enum PropListType { ObjectLiteral, ClassBody }; template class Parser : private JS::AutoGCRooter, public StrictModeGetter @@ -502,7 +504,11 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter return versionNumber() >= JSVERSION_1_7 || pc->isGenerator(); } - virtual bool strictMode() { return pc->sc->strict; } + virtual bool strictMode() { return pc->sc->strict(); } + bool setLocalStrictMode(bool strict) { + MOZ_ASSERT(tokenStream.debugHasNoLookahead()); + return pc->sc->setLocalStrictMode(strict); + } const ReadOnlyCompileOptions& options() const { return tokenStream.options(); @@ -546,6 +552,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter Node throwStatement(); Node tryStatement(); Node debuggerStatement(); + Node classStatement(); Node lexicalDeclaration(bool isConst); Node importDeclaration(); @@ -567,8 +574,8 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter Node parenExprOrGeneratorComprehension(); Node exprInParens(); - bool methodDefinition(Node literal, Node propname, FunctionType type, FunctionSyntaxKind kind, - GeneratorKind generatorKind, JSOp Op); + bool methodDefinition(PropListType listType, Node propList, Node propname, FunctionType type, + FunctionSyntaxKind kind, GeneratorKind generatorKind, JSOp Op); /* * Additional JS parsers. @@ -648,16 +655,21 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter Node pushLexicalScope(Handle blockObj, StmtInfoPC* stmt); Node pushLetScope(Handle blockObj, StmtInfoPC* stmt); bool noteNameUse(HandlePropertyName name, Node pn); - Node objectLiteral(); Node computedPropertyName(Node literal); Node arrayInitializer(); Node newRegExp(); - Node newBindingNode(PropertyName* name, bool functionScope, VarContext varContext = HoistVars); - bool checkDestructuring(BindData* data, Node left); - bool checkDestructuringObject(BindData* data, Node objectPattern); - bool checkDestructuringArray(BindData* data, Node arrayPattern); - bool bindDestructuringVar(BindData* data, Node pn); + Node propertyList(PropListType type); + Node newPropertyListNode(PropListType type); + + bool checkAndPrepareLexical(bool isConst, const TokenPos &errorPos); + Node makeInitializedLexicalBinding(HandlePropertyName name, bool isConst, const TokenPos &pos); + + Node newBindingNode(PropertyName *name, bool functionScope, VarContext varContext = HoistVars); + bool checkDestructuring(BindData *data, Node left); + bool checkDestructuringObject(BindData *data, Node objectPattern); + bool checkDestructuringArray(BindData *data, Node arrayPattern); + bool bindInitialized(BindData *data, Node pn); bool bindDestructuringLHS(Node pn); bool makeSetCall(Node pn, unsigned msg); Node cloneDestructuringDefault(Node opn); diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index fa429c0cde..b28fd73f8d 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -188,7 +188,8 @@ class SharedContext public: ExclusiveContext* const context; AnyContextFlags anyCxFlags; - bool strict; + bool strictScript; + bool localStrict; bool extraWarnings; // If it's function code, funbox must be non-nullptr and scopeChain must be @@ -196,7 +197,8 @@ class SharedContext SharedContext(ExclusiveContext* cx, Directives directives, bool extraWarnings) : context(cx), anyCxFlags(), - strict(directives.strict()), + strictScript(directives.strict()), + localStrict(false), extraWarnings(extraWarnings) {} @@ -218,9 +220,18 @@ class SharedContext inline bool allLocalsAliased(); + bool strict() { + return strictScript || localStrict; + } + bool setLocalStrictMode(bool strict) { + bool retVal = localStrict; + localStrict = strict; + return retVal; + } + // JSOPTION_EXTRA_WARNINGS warnings or strict mode errors. bool needStrictChecks() { - return strict || extraWarnings; + return strict() || extraWarnings; } bool isDotVariable(JSAtom* atom) const { diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 8bb9985f43..d703471008 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -186,10 +186,12 @@ class SyntaxParseHandler void addArrayElement(Node literal, Node element) { } Node newObjectLiteral(uint32_t begin) { return NodeGeneric; } + Node newClassMethodList(uint32_t begin) { return NodeGeneric; } bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; } bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; } bool addShorthand(Node literal, Node name, Node expr) { return true; } - bool addMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; } + bool addObjectMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; } + bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; } Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeUnparenthesizedYieldExpr; } Node newYieldStarExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; } diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index d4137567c7..071469d67b 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -110,6 +110,7 @@ macro(LET, "keyword 'let'") \ macro(EXPORT, "keyword 'export'") \ macro(IMPORT, "keyword 'import'") \ + macro(CLASS, "keyword 'class'") \ macro(RESERVED, "reserved keyword") \ /* reserved keywords in strict mode */ \ macro(STRICT_RESERVED, "reserved keyword") \ diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 1e73d16a49..c5fe319096 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -985,8 +985,14 @@ TokenStream::putIdentInTokenbuf(const char16_t* identStart) bool TokenStream::checkForKeyword(const KeywordInfo* kw, TokenKind* ttp) { - if (kw->tokentype == TOK_RESERVED) + if (kw->tokentype == TOK_RESERVED +#ifndef JS_HAS_CLASSES + || kw->tokentype == TOK_CLASS +#endif + ) + { return reportError(JSMSG_RESERVED_ID, kw->chars); + } if (kw->tokentype != TOK_STRICT_RESERVED) { if (kw->version <= versionNumber()) { diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index b291a9ed1c..fa09c579f5 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -534,9 +534,14 @@ class MOZ_STACK_CLASS TokenStream }; bool advance(size_t position); - void tell(Position*); - void seek(const Position& pos); - bool seek(const Position& pos, const TokenStream& other); + void tell(Position *); + void seek(const Position &pos); + bool seek(const Position &pos, const TokenStream &other); +#ifdef DEBUG + inline bool debugHasNoLookahead() const { + return lookahead == 0; + } +#endif const char16_t* rawCharPtrAt(size_t offset) const { return userbuf.rawCharPtrAt(offset); diff --git a/js/src/gc/Rooting.h b/js/src/gc/Rooting.h index 4c0d18f10a..cc8679b2b8 100644 --- a/js/src/gc/Rooting.h +++ b/js/src/gc/Rooting.h @@ -38,6 +38,7 @@ typedef JS::Handle HandleScriptSource; typedef JS::MutableHandle MutableHandleShape; typedef JS::MutableHandle MutableHandleAtom; typedef JS::MutableHandle MutableHandleNativeObject; +typedef JS::MutableHandle MutableHandlePlainObject; typedef JS::Rooted RootedNativeObject; typedef JS::Rooted RootedShape; diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index cd17fdb0eb..37ae099c54 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1974,7 +1974,19 @@ BaselineCompiler::emit_JSOP_INITPROP() return emitOpIC(compiler.getStub(&stubSpace_)); } -typedef bool (*NewbornArrayPushFn)(JSContext*, HandleObject, const Value&); +bool +BaselineCompiler::emit_JSOP_INITLOCKEDPROP() +{ + return emit_JSOP_INITPROP(); +} + +bool +BaselineCompiler::emit_JSOP_INITHIDDENPROP() +{ + return emit_JSOP_INITPROP(); +} + +typedef bool (*NewbornArrayPushFn)(JSContext *, HandleObject, const Value &); static const VMFunction NewbornArrayPushInfo = FunctionInfo(NewbornArrayPush); bool diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 2dba2ef86e..2f520241a4 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -103,6 +103,8 @@ namespace jit { _(JSOP_INITELEM_INC) \ _(JSOP_MUTATEPROTO) \ _(JSOP_INITPROP) \ + _(JSOP_INITLOCKEDPROP) \ + _(JSOP_INITHIDDENPROP) \ _(JSOP_INITPROP_GETTER) \ _(JSOP_INITPROP_SETTER) \ _(JSOP_ARRAYPUSH) \ diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 08540ac4ae..87de08c5ee 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -8263,6 +8263,8 @@ DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_ op == JSOP_SETGNAME || op == JSOP_STRICTSETGNAME || op == JSOP_INITPROP || + op == JSOP_INITLOCKEDPROP || + op == JSOP_INITHIDDENPROP || op == JSOP_SETALIASEDVAR || op == JSOP_INITALIASEDLEXICAL); @@ -8296,10 +8298,14 @@ DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_ return false; } - if (op == JSOP_INITPROP) { + if (op == JSOP_INITPROP || + op == JSOP_INITLOCKEDPROP || + op == JSOP_INITHIDDENPROP) + { MOZ_ASSERT(obj->is()); + unsigned propAttrs = GetInitDataPropAttrs(op); if (!NativeDefineProperty(cx, obj.as(), id, rhs, - nullptr, nullptr, JSPROP_ENUMERATE)) + nullptr, nullptr, propAttrs)) { return false; } diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index a4ae40eb4a..4a551b964a 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -4601,16 +4601,16 @@ CodeGenerator::visitMutateProto(LMutateProto* lir) callVM(MutatePrototypeInfo, lir); } -typedef bool(*InitPropFn)(JSContext* cx, HandleNativeObject obj, - HandlePropertyName name, HandleValue value); -static const VMFunction InitPropInfo = - FunctionInfo(InitProp); +typedef bool(*InitPropFn)(JSContext *cx, HandleNativeObject obj, + HandlePropertyName name, HandleValue value, jsbytecode *pc); +static const VMFunction InitPropInfo = FunctionInfo(InitProp); void CodeGenerator::visitInitProp(LInitProp* lir) { Register objReg = ToRegister(lir->getObject()); + pushArg(ImmPtr(lir->mir()->resumePoint()->pc())); pushArg(ToValue(lir, LInitProp::ValueIndex)); pushArg(ImmGCPtr(lir->mir()->propertyName())); pushArg(objReg); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 02b3bf0f62..c19a5f9944 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1765,6 +1765,8 @@ IonBuilder::inspectOpcode(JSOp op) return jsop_initelem_array(); case JSOP_INITPROP: + case JSOP_INITLOCKEDPROP: + case JSOP_INITHIDDENPROP: { PropertyName* name = info().getAtom(pc)->asPropertyName(); return jsop_initprop(name); @@ -6471,7 +6473,7 @@ IonBuilder::jsop_initprop(PropertyName* name) if (useSlowPath) { // JSOP_NEWINIT becomes an MNewObject without preconfigured properties. - MInitProp* init = MInitProp::New(alloc(), obj, name, value); + MInitProp *init = MInitProp::New(alloc(), obj, name, value); current->add(init); return resumeAfter(init); } @@ -6495,7 +6497,7 @@ IonBuilder::jsop_initprop(PropertyName* name) return resumeAfter(store); } - MSlots* slots = MSlots::New(alloc(), obj); + MSlots *slots = MSlots::New(alloc(), obj); current->add(slots); uint32_t slot = templateObject->dynamicSlotIndex(shape->slot()); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 90093c8e2e..765c309a1a 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -3322,11 +3322,10 @@ class MInitProp : public MAryInstruction<2>, public MixPolicy, BoxPolicy<1> >::Data { - public: AlwaysTenuredPropertyName name_; protected: - MInitProp(MDefinition* obj, PropertyName* name, MDefinition* value) + MInitProp(MDefinition *obj, PropertyName *name, MDefinition *value) : name_(name) { initOperand(0, obj); @@ -3337,23 +3336,24 @@ class MInitProp public: INSTRUCTION_HEADER(InitProp) - static MInitProp* New(TempAllocator& alloc, MDefinition* obj, PropertyName* name, - MDefinition* value) + static MInitProp *New(TempAllocator &alloc, MDefinition *obj, PropertyName *name, + MDefinition *value) { return new(alloc) MInitProp(obj, name, value); } - MDefinition* getObject() const { + MDefinition *getObject() const { return getOperand(0); } - MDefinition* getValue() const { + MDefinition *getValue() const { return getOperand(1); } - PropertyName* propertyName() const { + PropertyName *propertyName() const { return name_; } - bool possiblyCalls() const override { + + bool possiblyCalls() const MOZ_OVERRIDE { return true; } }; diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 5ca42f33d5..cde41159e8 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -192,15 +192,17 @@ MutatePrototype(JSContext* cx, HandlePlainObject obj, HandleValue value) } bool -InitProp(JSContext* cx, HandleNativeObject obj, HandlePropertyName name, HandleValue value) +InitProp(JSContext *cx, HandleNativeObject obj, HandlePropertyName name, HandleValue value, + jsbytecode *pc) { RootedId id(cx, NameToId(name)); - return NativeDefineProperty(cx, obj, id, value, nullptr, nullptr, JSPROP_ENUMERATE); + unsigned propAttrs = GetInitDataPropAttrs(JSOp(*pc)); + return NativeDefineProperty(cx, obj, id, value, nullptr, nullptr, propAttrs); } template bool -LooselyEqual(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +LooselyEqual(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { if (!js::LooselyEqual(cx, lhs, rhs, res)) return false; diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index e957164e09..a405fc3d79 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -644,13 +644,15 @@ bool CheckOverRecursed(JSContext* cx); bool CheckOverRecursedWithExtra(JSContext* cx, BaselineFrame* frame, uint32_t extra, uint32_t earlyCheck); -bool DefVarOrConst(JSContext* cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeChain); -bool SetConst(JSContext* cx, HandlePropertyName name, HandleObject scopeChain, HandleValue rval); -bool MutatePrototype(JSContext* cx, HandlePlainObject obj, HandleValue value); -bool InitProp(JSContext* cx, HandleNativeObject obj, HandlePropertyName name, HandleValue value); +bool DefVarOrConst(JSContext *cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeChain); +bool SetConst(JSContext *cx, HandlePropertyName name, HandleObject scopeChain, HandleValue rval); +bool MutatePrototype(JSContext *cx, HandlePlainObject obj, HandleValue value); + +bool InitProp(JSContext *cx, HandleNativeObject obj, HandlePropertyName name, HandleValue value, + jsbytecode *pc); template -bool LooselyEqual(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res); +bool LooselyEqual(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res); template bool StrictlyEqual(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res); diff --git a/js/src/js.msg b/js/src/js.msg index 0916919a56..d0cf9b7632 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -191,6 +191,7 @@ MSG_DEF(JSMSG_BAD_GENERATOR_RETURN, 1, JSEXN_TYPEERR, "generator function {0} MSG_DEF(JSMSG_BAD_GENERATOR_SYNTAX, 1, JSEXN_SYNTAXERR, "{0} expression must be parenthesized") MSG_DEF(JSMSG_BAD_GENEXP_BODY, 1, JSEXN_SYNTAXERR, "illegal use of {0} in generator expression") MSG_DEF(JSMSG_BAD_INCOP_OPERAND, 0, JSEXN_REFERENCEERR, "invalid increment/decrement operand") +MSG_DEF(JSMSG_BAD_METHOD_DEF, 0, JSEXN_SYNTAXERR, "bad method definition") MSG_DEF(JSMSG_BAD_OCTAL, 1, JSEXN_SYNTAXERR, "{0} is not a legal ECMA-262 octal constant") MSG_DEF(JSMSG_BAD_OPERAND, 1, JSEXN_SYNTAXERR, "invalid {0} operand") MSG_DEF(JSMSG_BAD_PROP_ID, 0, JSEXN_SYNTAXERR, "invalid property id") @@ -204,6 +205,7 @@ MSG_DEF(JSMSG_CATCH_AFTER_GENERAL, 0, JSEXN_SYNTAXERR, "catch after uncondit MSG_DEF(JSMSG_CATCH_IDENTIFIER, 0, JSEXN_SYNTAXERR, "missing identifier in catch") MSG_DEF(JSMSG_CATCH_OR_FINALLY, 0, JSEXN_SYNTAXERR, "missing catch or finally after try") MSG_DEF(JSMSG_CATCH_WITHOUT_TRY, 0, JSEXN_SYNTAXERR, "catch without try") +MSG_DEF(JSMSG_CLASS_NOT_IMPLEMENTED, 0, JSEXN_SYNTAXERR, "classes are not implemented yet") MSG_DEF(JSMSG_COLON_AFTER_CASE, 0, JSEXN_SYNTAXERR, "missing : after case label") MSG_DEF(JSMSG_COLON_AFTER_ID, 0, JSEXN_SYNTAXERR, "missing : after property id") MSG_DEF(JSMSG_COLON_IN_COND, 0, JSEXN_SYNTAXERR, "missing : in conditional expression") @@ -217,6 +219,7 @@ MSG_DEF(JSMSG_CURLY_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing } after prop MSG_DEF(JSMSG_CURLY_AFTER_TRY, 0, JSEXN_SYNTAXERR, "missing } after try block") MSG_DEF(JSMSG_CURLY_BEFORE_BODY, 0, JSEXN_SYNTAXERR, "missing { before function body") MSG_DEF(JSMSG_CURLY_BEFORE_CATCH, 0, JSEXN_SYNTAXERR, "missing { before catch block") +MSG_DEF(JSMSG_CURLY_BEFORE_CLASS, 0, JSEXN_SYNTAXERR, "missing { before class body") MSG_DEF(JSMSG_CURLY_BEFORE_LET, 0, JSEXN_SYNTAXERR, "missing { before let block") MSG_DEF(JSMSG_CURLY_BEFORE_FINALLY, 0, JSEXN_SYNTAXERR, "missing { before finally block") MSG_DEF(JSMSG_CURLY_BEFORE_SWITCH, 0, JSEXN_SYNTAXERR, "missing { before switch body") @@ -244,6 +247,7 @@ MSG_DEF(JSMSG_INVALID_FOR_IN_INIT, 0, JSEXN_SYNTAXERR, "for-in loop let decl MSG_DEF(JSMSG_INVALID_FOR_OF_INIT, 0, JSEXN_SYNTAXERR, "for-of loop variable declaration may not have an initializer") MSG_DEF(JSMSG_IN_AFTER_FOR_NAME, 0, JSEXN_SYNTAXERR, "missing 'in' or 'of' after for") MSG_DEF(JSMSG_LABEL_NOT_FOUND, 0, JSEXN_SYNTAXERR, "label not found") +MSG_DEF(JSMSG_LET_CLASS_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a class") MSG_DEF(JSMSG_LET_COMP_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a comprehension variable") MSG_DEF(JSMSG_LEXICAL_DECL_NOT_IN_BLOCK, 1, JSEXN_SYNTAXERR, "{0} declaration not directly within block") MSG_DEF(JSMSG_LINE_BREAK_AFTER_THROW, 0, JSEXN_SYNTAXERR, "no line break is allowed between 'throw' and its expression") @@ -259,6 +263,7 @@ MSG_DEF(JSMSG_MODULE_SPEC_AFTER_FROM, 0, JSEXN_SYNTAXERR, "missing module speci MSG_DEF(JSMSG_NAME_AFTER_DOT, 0, JSEXN_SYNTAXERR, "missing name after . operator") MSG_DEF(JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT, 0, JSEXN_SYNTAXERR, "parameter(s) with default followed by parameter without default") MSG_DEF(JSMSG_NO_BINDING_NAME, 0, JSEXN_SYNTAXERR, "missing binding name") +MSG_DEF(JSMSG_NO_CLASS_CONSTRUCTOR, 0, JSEXN_TYPEERR, "class default constructors not yet implemented") MSG_DEF(JSMSG_NO_EXPORT_NAME, 0, JSEXN_SYNTAXERR, "missing export name") MSG_DEF(JSMSG_NO_IMPORT_NAME, 0, JSEXN_SYNTAXERR, "missing import name") MSG_DEF(JSMSG_NO_VARIABLE_NAME, 0, JSEXN_SYNTAXERR, "missing variable name") @@ -305,6 +310,7 @@ MSG_DEF(JSMSG_TOO_MANY_LOCALS, 0, JSEXN_SYNTAXERR, "too many local varia MSG_DEF(JSMSG_TOO_MANY_YIELDS, 0, JSEXN_SYNTAXERR, "too many yield expressions") MSG_DEF(JSMSG_TOUGH_BREAK, 0, JSEXN_SYNTAXERR, "unlabeled break must be inside loop or switch") MSG_DEF(JSMSG_UNEXPECTED_TOKEN, 2, JSEXN_SYNTAXERR, "expected {0}, got {1}") +MSG_DEF(JSMSG_UNNAMED_CLASS_STMT, 0, JSEXN_SYNTAXERR, "class statement requires a name") MSG_DEF(JSMSG_UNNAMED_FUNCTION_STMT, 0, JSEXN_SYNTAXERR, "function statement requires a name") MSG_DEF(JSMSG_UNTERMINATED_COMMENT, 0, JSEXN_SYNTAXERR, "unterminated comment") MSG_DEF(JSMSG_UNTERMINATED_REGEXP, 0, JSEXN_SYNTAXERR, "unterminated regular expression literal") diff --git a/js/src/jsast.tbl b/js/src/jsast.tbl index 286224fa08..245e9b0dcf 100644 --- a/js/src/jsast.tbl +++ b/js/src/jsast.tbl @@ -76,4 +76,6 @@ ASTDEF(AST_TAGGED_TEMPLATE, "TaggedTemplate", "taggedTempl ASTDEF(AST_CALL_SITE_OBJ, "CallSiteObject", "callSiteObject") ASTDEF(AST_COMPUTED_NAME, "ComputedName", "computedName") +ASTDEF(AST_CLASS_STMT, "ClassStatement", "classStatement") +ASTDEF(AST_CLASS_METHOD, "ClassMethod", "classMethod") /* AST_LIMIT = last + 1 */ diff --git a/js/src/jsreflect.cpp b/js/src/jsreflect.cpp index db354a4002..f81f60fc96 100644 --- a/js/src/jsreflect.cpp +++ b/js/src/jsreflect.cpp @@ -618,6 +618,10 @@ class NodeBuilder bool exportBatchSpecifier(TokenPos* pos, MutableHandleValue dst); + bool classStatement(HandleValue name, HandleValue heritage, HandleValue block, TokenPos *pos, MutableHandleValue dst); + bool classMethods(NodeVector &methods, MutableHandleValue dst); + bool classMethod(HandleValue name, HandleValue body, PropKind kind, bool isStatic, TokenPos *pos, MutableHandleValue dst); + /* * expressions */ @@ -1696,6 +1700,53 @@ NodeBuilder::function(ASTType type, TokenPos* pos, dst); } +bool +NodeBuilder::classMethod(HandleValue name, HandleValue body, PropKind kind, bool isStatic, + TokenPos *pos, MutableHandleValue dst) +{ + RootedValue kindName(cx); + if (!atomValue(kind == PROP_INIT + ? "method" + : kind == PROP_GETTER + ? "get" + : "set", &kindName)) { + return false; + } + + RootedValue isStaticVal(cx, BooleanValue(isStatic)); + RootedValue cb(cx, callbacks[AST_CLASS_METHOD]); + if (!cb.isNull()) + return callback(cb, kindName, name, body, isStaticVal, pos, dst); + + return newNode(AST_CLASS_METHOD, pos, + "name", name, + "body", body, + "kind", kindName, + "static", isStaticVal, + dst); +} + +bool +NodeBuilder::classMethods(NodeVector &methods, MutableHandleValue dst) +{ + return newArray(methods, dst); +} + +bool +NodeBuilder::classStatement(HandleValue name, HandleValue heritage, HandleValue block, + TokenPos *pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_CLASS_STMT]); + if (!cb.isNull()) + return callback(cb, name, heritage, block, pos, dst); + + return newNode(AST_CLASS_STMT, pos, + "name", name, + "heritage", heritage, + "body", block, + dst); +} + namespace { /* @@ -1769,6 +1820,8 @@ class ASTSerializer bool propertyName(ParseNode* pn, MutableHandleValue dst); bool property(ParseNode* pn, MutableHandleValue dst); + bool classMethod(ParseNode *pn, MutableHandleValue dst); + bool optIdentifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst) { if (!atom) { dst.setMagic(JS_SERIALIZE_NO_NODE); @@ -2549,6 +2602,35 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) case PNK_DEBUGGER: return builder.debuggerStatement(&pn->pn_pos, dst); + case PNK_CLASS: + { + RootedValue className(cx); + RootedValue heritage(cx); + RootedValue classBody(cx); + return identifier(pn->pn_kid1->as().innerBinding(), &className) && + optExpression(pn->pn_kid2, &heritage) && + statement(pn->pn_kid3, &classBody) && + builder.classStatement(className, heritage, classBody, &pn->pn_pos, dst); + } + + case PNK_CLASSMETHODLIST: + { + NodeVector methods(cx); + if (!methods.reserve(pn->pn_count)) + return false; + + for (ParseNode *next = pn->pn_head; next; next = next->pn_next) { + MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); + + RootedValue prop(cx); + if (!classMethod(next, &prop)) + return false; + methods.infallibleAppend(prop); + } + + return builder.classMethods(methods, dst); + } + case PNK_NOP: return builder.emptyStatement(&pn->pn_pos, dst); @@ -2557,6 +2639,34 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) } } +bool +ASTSerializer::classMethod(ParseNode *pn, MutableHandleValue dst) +{ + PropKind kind; + switch (pn->getOp()) { + case JSOP_INITPROP: + kind = PROP_INIT; + break; + + case JSOP_INITPROP_GETTER: + kind = PROP_GETTER; + break; + + case JSOP_INITPROP_SETTER: + kind = PROP_SETTER; + break; + + default: + LOCAL_NOT_REACHED("unexpected object-literal property"); + } + + RootedValue key(cx), val(cx); + bool isStatic = pn->as().isStatic(); + return propertyName(pn->pn_left, &key) && + expression(pn->pn_right, &val) && + builder.classMethod(key, val, kind, isStatic, &pn->pn_pos, dst); +} + bool ASTSerializer::leftAssociate(ParseNode* pn, MutableHandleValue dst) { diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 7a7559aebe..1d163ebdd6 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -2606,7 +2606,7 @@ JSScript::fullyInitFromEmitter(ExclusiveContext* cx, HandleScript script, Byteco bce->tryNoteList.finish(script->trynotes()); if (bce->blockScopeList.length() != 0) bce->blockScopeList.finish(script->blockScopes()); - script->strict_ = bce->sc->strict; + script->strict_ = bce->sc->strict(); script->explicitUseStrict_ = bce->sc->hasExplicitUseStrict(); script->bindingsAccessedDynamically_ = bce->sc->bindingsAccessedDynamically(); script->funHasExtensibleScope_ = funbox ? funbox->hasExtensibleScope() : false; diff --git a/js/src/jsversion.h b/js/src/jsversion.h index fa171d26a6..bc2f833c13 100644 --- a/js/src/jsversion.h +++ b/js/src/jsversion.h @@ -41,4 +41,9 @@ */ #define JS_OLD_GETTER_SETTER_METHODS 1 +/* Support for ES6 Classes. */ +#ifdef NIGHTLY_BUILD +#define JS_HAS_CLASSES 1 +#endif + #endif /* jsversion_h */ diff --git a/js/src/tests/browser.js b/js/src/tests/browser.js index 624ac89cf3..95961e72e3 100644 --- a/js/src/tests/browser.js +++ b/js/src/tests/browser.js @@ -332,6 +332,10 @@ function jsTestDriverBrowserInit() { properties.version = '1.8'; } + else if (properties.test.match(/^ecma_6\/Class/)) + { + properties.version = '1.8'; + } } // default to language=type;text/javascript. required for diff --git a/js/src/tests/ecma_6/Class/classPrototype.js b/js/src/tests/ecma_6/Class/classPrototype.js new file mode 100644 index 0000000000..17bbf21257 --- /dev/null +++ b/js/src/tests/ecma_6/Class/classPrototype.js @@ -0,0 +1,27 @@ +var test = ` + +// The prototype of a class is a non-writable, non-configurable, non-enumerable data property. +class a { constructor() { } } +var protoDesc = Object.getOwnPropertyDescriptor(a, "prototype"); +assertEq(protoDesc.writable, false); +assertEq(protoDesc.configurable, false); +assertEq(protoDesc.enumerable, false); + +var prototype = protoDesc.value; +assertEq(typeof prototype, "object"); +assertEq(Object.getPrototypeOf(prototype), Object.prototype); +assertEq(Object.isExtensible(prototype), true); + +var desiredPrototype = {}; +Object.defineProperty(desiredPrototype, "constructor", { writable: true, + configurable: true, + enumerable: false, + value: a }); +assertDeepEq(prototype, desiredPrototype); +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/ecma_6/Class/constructorCalled.js b/js/src/tests/ecma_6/Class/constructorCalled.js new file mode 100644 index 0000000000..bf77362e2d --- /dev/null +++ b/js/src/tests/ecma_6/Class/constructorCalled.js @@ -0,0 +1,31 @@ +// The constructor specified should get called, regardless of order, or +// other distractions + +var test = ` + +var called = false; +class a { constructor(x) { assertEq(x, 4); called = true } } +new a(4); +assertEq(called, true); + +called = false; +class b { constructor() { called = true } method() { } } +new b(); +assertEq(called, true); + +called = false; +class c { method() { } constructor() { called = true; } } +new c(); +assertEq(called, true); + +called = false; +class d { [\"constructor\"]() { throw new Error(\"NO\"); } constructor() { called = true; } } +new d(); +assertEq(called, true); +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/ecma_6/Class/innerBinding.js b/js/src/tests/ecma_6/Class/innerBinding.js new file mode 100644 index 0000000000..55d1ffca3f --- /dev/null +++ b/js/src/tests/ecma_6/Class/innerBinding.js @@ -0,0 +1,78 @@ +// Class statements should create an immutable inner binding. Since all code in +// classes is in strict mode, attempts to mutate it should throw. + +if (classesEnabled()) { + +// XXXefaust Because we currently try to do assignment to const as an early error, +// this is a syntax error. It is specced to be a TypeError + +function syntaxWrapper() { + eval("class Foo { constructor() { } tryBreak() { Foo = 4; } }"); +} +assertThrowsInstanceOf(syntaxWrapper, SyntaxError); +/* +var test = ` +class Foo { constructor() { }; tryBreak() { Foo = 4; } } +assertThrowsInstanceOf(() => new Foo().tryBreak(), TypeError); + +{ + class foo { constructor() { }; tryBreak() { foo = 4; } } + assertThrowsInstanceOf(() => new foo().tryBreak(), TypeError); +} +`; +*/ + +var test = ` + +// TDZ applies to inner bindings +assertThrowsInstanceOf(()=>eval(\`class Bar { + constructor() { }; + [Bar] () { }; + }\`), ReferenceError); + +// There's no magic "inner binding" global +{ + class Foo { + constructor() { }; + test() { + class Bar { + constructor() { } + test() { return Foo === Bar } + } + return new Bar().test(); + } + } + assertEq(new Foo().test(), false); +} + +// Inner bindings are shadowable +{ + class Foo { + constructor() { } + test(Foo) { return Foo; } + } + assertEq(new Foo().test(4), 4); +} + +// The outer binding is distinct from the inner one +{ + let orig_X; + + class X { + constructor() { } + f() { assertEq(X, orig_X); } + } + + orig_X = X; + X = 13; + assertEq(X, 13); + new orig_X().f(); +} +`; + +eval(test); + +} + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/ecma_6/Class/methDefn.js b/js/src/tests/ecma_6/Class/methDefn.js index b8d2275c2b..cfaaa78ca5 100644 --- a/js/src/tests/ecma_6/Class/methDefn.js +++ b/js/src/tests/ecma_6/Class/methDefn.js @@ -165,6 +165,30 @@ assertEq(a.b(1), 1); a = {["b"](c){"use strict";return c;}}; assertEq(a.b(1), 1); +// Allow strict-reserved names as methods in objects. +// (Bug 1124362) +a = { static() { return 4; } }; +assertEq(a.static(), 4); + +a = { get static() { return 4; } }; +assertEq(a.static, 4); + +a = { set static(x) { assertEq(x, 4); } }; +a.static = 4; + +function testStrictMode() { + "use strict"; + var obj = { static() { return 4; } }; + assertEq(obj.static(), 4); + + obj = { get static() { return 4; } }; + assertEq(obj.static, 4); + + obj = { set static(x) { assertEq(x, 4); } }; + obj.static = 4; +} +testStrictMode(); + // Tests provided by benvie in the bug to distinguish from ES5 desugar. assertEq(({ method() {} }).method.name, "method"); assertThrowsInstanceOf(function() {({ method() { method() } }).method() }, ReferenceError); diff --git a/js/src/tests/ecma_6/Class/methodInstallation.js b/js/src/tests/ecma_6/Class/methodInstallation.js new file mode 100644 index 0000000000..56b293e98a --- /dev/null +++ b/js/src/tests/ecma_6/Class/methodInstallation.js @@ -0,0 +1,52 @@ +// Do the things we write in classes actually appear as they are supposed to? + +var test= ` + +var methodCalled = false; +var getterCalled = false; +var setterCalled = false; +var constructorCalled = false; +class a { + constructor() { constructorCalled = true; } + __proto__() { methodCalled = true } + get getter() { getterCalled = true; } + set setter(x) { setterCalled = true; } + *[Symbol.iterator]() { yield "cow"; yield "pig"; } +} +var aConstDesc = Object.getOwnPropertyDescriptor(a.prototype, \"constructor\"); +assertEq(aConstDesc.writable, true); +assertEq(aConstDesc.configurable, true); +assertEq(aConstDesc.enumerable, false); +aConstDesc.value(); +assertEq(constructorCalled, true); + +// __proto__ is just an identifier for classes. No prototype changes are made. +assertEq(Object.getPrototypeOf(a.prototype), Object.prototype); +var aMethDesc = Object.getOwnPropertyDescriptor(a.prototype, \"__proto__\"); +assertEq(aMethDesc.writable, true); +assertEq(aMethDesc.configurable, true); +assertEq(aMethDesc.enumerable, true); +aMethDesc.value(); +assertEq(methodCalled, true); + +var aGetDesc = Object.getOwnPropertyDescriptor(a.prototype, \"getter\"); +assertEq(aGetDesc.configurable, true); +assertEq(aGetDesc.enumerable, true); +aGetDesc.get(); +assertEq(getterCalled, true); + +var aSetDesc = Object.getOwnPropertyDescriptor(a.prototype, \"setter\"); +assertEq(aSetDesc.configurable, true); +assertEq(aSetDesc.enumerable, true); +aSetDesc.set(); +assertEq(setterCalled, true); +assertDeepEq(aSetDesc, Object.getOwnPropertyDescriptor(a.prototype, \"setter\")); + +assertEq([...new a()].join(), "cow,pig"); +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/ecma_6/Class/methodOverwrites.js b/js/src/tests/ecma_6/Class/methodOverwrites.js new file mode 100644 index 0000000000..9af84a212c --- /dev/null +++ b/js/src/tests/ecma_6/Class/methodOverwrites.js @@ -0,0 +1,45 @@ +// Ensure that we can overwrite methods when more tha one is present. + +var test = ` +var result = 0; +// Regardless of order, the constructor is overridden by any CPN, because it's +// processed seperately. +class a { [\"constructor\"]() { result += 1; }; constructor() { result += 2; } } +var aInst = new a(); +assertEq(result, 2); +aInst.constructor(); +assertEq(result, 3); + +class b { constructor() { result += 2; } [\"constructor\"]() { result += 1; }; } +var bInst = new b(); +assertEq(result, 5); +bInst.constructor(); +assertEq(result, 6); + +class c { constructor() { } method() { result += 1 } get method() { result += 2 } } +new c().method; +assertEq(result, 8); + +class d { constructor() { } get method() { result += 1 } method() { result += 2 } } +new d().method(); +assertEq(result, 10); + +// getters and setter should not overwrite each other, but merge cleanly. +class e { constructor() { } get method() { result += 1 } set method(x) { } } +new e().method; +assertEq(result, 11); + +class f { constructor() { } + set method(x) { throw "NO"; } + method() { throw "NO" } + get method() { return new Function("result += 1"); } + } +new f().method(); +assertEq(result, 12); +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/ecma_6/Class/outerBinding.js b/js/src/tests/ecma_6/Class/outerBinding.js new file mode 100644 index 0000000000..66595bb5c7 --- /dev/null +++ b/js/src/tests/ecma_6/Class/outerBinding.js @@ -0,0 +1,63 @@ +// A class creates a mutable lexical outer binding. + +var test = ` +class Foo { constructor() { } } +assertEq(typeof Foo, \"function\"); +Foo = 5; +assertEq(Foo, 5); + +{ + class foo { constructor() { } } + assertEq(typeof foo, \"function\"); + foo = 4; + assertEq(foo, 4); +} + +var ieval = eval; + +{ + class PermanentBinding { constructor() { } } + delete PermanentBinding; + // That...didn't actually work, right? + assertEq(typeof PermanentBinding, "function"); +} + +{ + try { + ieval(\`class x { constructor () { } } + throw new Error("FAIL"); + class y { constructor () { } } + \`); + } catch (e if e instanceof Error) { } + assertEq(typeof x, "function"); + assertEq(y, undefined, "Congrats, you fixed top-level lexical scoping! " + + "Please uncomment the tests below for the real test."); + // assertThrowsInstanceOf(() => y, ReferenceError); +} + +/* +===== UNCOMMENT ME WHEN ENABLING THE TEST ABOVE. ===== +const globalConstant = 0; +var earlyError = true; +try { + ieval("earlyError = false; class globalConstant { constructor() { } }"); +} catch (e if e instanceof TypeError) { } +assertEq(earlyError, true); +*/ + +function strictEvalShadows() { + "use strict"; + let x = 4; + eval(\`class x { constructor() { } } + assertEq(typeof x, "function"); + \`); + assertEq(x, 4); +} +strictEvalShadows() +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/ecma_6/Class/shell.js b/js/src/tests/ecma_6/Class/shell.js index e69de29bb2..5c4d61268e 100644 --- a/js/src/tests/ecma_6/Class/shell.js +++ b/js/src/tests/ecma_6/Class/shell.js @@ -0,0 +1,12 @@ +// Enable "let" in shell builds. So silly. +if (typeof version != 'undefined') + version(185); + +function classesEnabled() { + try { + new Function("class Foo { constructor() { } }"); + return true; + } catch (e if e instanceof SyntaxError) { + return false; + } +} diff --git a/js/src/tests/ecma_6/Class/strictExecution.js b/js/src/tests/ecma_6/Class/strictExecution.js new file mode 100644 index 0000000000..4d17dea4f8 --- /dev/null +++ b/js/src/tests/ecma_6/Class/strictExecution.js @@ -0,0 +1,20 @@ +// Classes are always strict mode. Check computed property names as well. + +var test = ` +class a { constructor() { Object.preventExtensions({}).prop = 0; } } +assertThrowsInstanceOf(() => new a(), TypeError); + +function shouldThrow() { + class b { + [Object.preventExtensions({}).prop = 4]() { } + constructor() { } + } +} +assertThrowsInstanceOf(shouldThrow, TypeError); +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === "function") + reportCompare(0, 0, "OK"); diff --git a/js/src/tests/js1_8_5/extensions/reflect-parse.js b/js/src/tests/js1_8_5/extensions/reflect-parse.js index 88c964c9fa..9bb0cb334a 100644 --- a/js/src/tests/js1_8_5/extensions/reflect-parse.js +++ b/js/src/tests/js1_8_5/extensions/reflect-parse.js @@ -67,6 +67,18 @@ function defaultClause(stmts) Pattern({ type: "SwitchCase", test: null, conseque function catchClause(id, guard, body) Pattern({ type: "CatchClause", param: id, guard: guard, body: body }) function tryStmt(body, guarded, unguarded, fin) Pattern({ type: "TryStatement", block: body, guardedHandlers: guarded, handler: unguarded, finalizer: fin }) function letStmt(head, body) Pattern({ type: "LetStatement", head: head, body: body }) + +function classStmt(id, heritage, body) Pattern({ type: "ClassStatement", + name: id, + heritage: heritage, + body: body}) +function classMethod(id, body, kind, static) Pattern({ type: "ClassMethod", + name: id, + body: body, + kind: kind, + static: static }) + + function funExpr(id, args, body, gen) Pattern({ type: "FunctionExpression", id: id, params: args, @@ -1134,6 +1146,149 @@ try { } catch (e) { } +// Classes +function classesEnabled() { + try { + Reflect.parse("class foo { constructor() { } }"); + return true; + } catch (e) { + assertEq(e instanceof SyntaxError, true); + return false; + } +} + +function testClasses() { + // No unnamed class statements. + assertError("class { constructor() { } }", SyntaxError); + + function simpleMethod(id, kind, generator, args=[]) { + assertEq(generator && kind === "method", generator); + let idN = ident(id); + let methodMaker = generator ? genFunExpr : funExpr; + let methodName = kind !== "method" ? null : idN; + let methodFun = methodMaker(methodName, args.map(ident), blockStmt([])); + + return classMethod(idN, methodFun, kind, false); + } + function setClassMethods(class_, methods) { + class_.template.body = methods; + } + + let simpleConstructor = simpleMethod("constructor", "method", false); + let emptyFooClass = classStmt(ident("Foo"), null, [simpleConstructor]); + + /* Trivial classes */ + assertStmt("class Foo { constructor() { } }", emptyFooClass); + + // Allow methods and accessors + let stmt = classStmt(ident("Foo"), null, + [simpleConstructor, simpleMethod("method", "method", false)]); + assertStmt("class Foo { constructor() { } method() { } }", stmt); + + setClassMethods(stmt, [simpleConstructor, simpleMethod("method", "get", false)]); + assertStmt("class Foo { constructor() { } get method() { } }", stmt); + + setClassMethods(stmt, [simpleConstructor, simpleMethod("method", "set", false, ["x"])]); + assertStmt("class Foo { constructor() { } set method(x) { } }", stmt); + + /* Constructor */ + // Currently, we do not allow default constructors + assertError("class Foo { }", TypeError); + + // It is an error to have two methods named constructor, but not other + // names, regardless if one is an accessor or a generator. + assertError("class Foo { constructor() { } constructor(a) { } }", SyntaxError); + let methods = [["method() { }", simpleMethod("method", "method", false)], + ["*method() { }", simpleMethod("method", "method", true)], + ["get method() { }", simpleMethod("method", "get", false)], + ["set method(x) { }", simpleMethod("method", "set", false, ["x"])]]; + let i,j; + for (i=0; i < methods.length; i++) { + for (j=0; j < methods.length; j++) { + setClassMethods(stmt, + [simpleConstructor, + methods[i][1], + methods[j][1]]); + let str = "class Foo { constructor() { } " + + methods[i][0] + " " + methods[j][0] + + " }"; + assertStmt(str, stmt); + } + } + + // It is, however, not an error to have a constructor, and a method with a + // computed property name 'constructor' + assertStmt("class Foo { constructor () { } [\"constructor\"] () { } }", + classStmt(ident("Foo"), null, + [simpleConstructor, + classMethod(computedName(lit("constructor")), + funExpr(null, [], blockStmt([])), + "method", false)])); + + // It is an error to have a generator or accessor named constructor + assertError("class Foo { *constructor() { } }", SyntaxError); + assertError("class Foo { get constructor() { } }", SyntaxError); + assertError("class Foo { set constructor() { } }", SyntaxError); + + /* Semicolons */ + // Allow Semicolons in Class Definitions + assertStmt("class Foo { constructor() { }; }", emptyFooClass); + + // Allow more than one semicolon, even in otherwise trivial classses + assertStmt("class Foo { ;;; constructor() { } }", emptyFooClass); + + // Semicolons are optional, even if the methods share a line + setClassMethods(stmt, [simpleMethod("method", "method", false), simpleConstructor]); + assertStmt("class Foo { method() { } constructor() { } }", stmt); + + /* Generators */ + // No yield as a class name inside a generator + assertError("function *foo() {\ + class yield {\ + constructor() { } \ + }\ + }", SyntaxError); + + // Methods may be generators, but not accessors + assertError("class Foo { constructor() { } *get foo() { } }", SyntaxError); + assertError("class Foo { constructor() { } *set foo() { } }", SyntaxError); + + setClassMethods(stmt, [simpleMethod("method", "method", true), simpleConstructor]); + assertStmt("class Foo { *method() { } constructor() { } }", stmt); + + /* Strictness */ + // yield is a strict-mode keyword, and class definitions are always strict. + assertError("class Foo { constructor() { var yield; } }", SyntaxError); + // Beware of the strictness of computed property names. Here use bareword + // deletion (a deprecated action) to check. + assertError("class Foo { constructor() { } [delete bar]() { }}", SyntaxError); + + /* Bindings */ + // Classes should bind lexically, so they should collide with other in-block + // lexical bindings + assertError("{ let Foo; class Foo { constructor() { } } }", TypeError); + assertError("{ const Foo = 0; class Foo { constructor() { } } }", TypeError); + assertError("{ class Foo { constructor() { } } class Foo { constructor() { } } }", TypeError); + + // Can't make a lexical binding inside a block. + assertError("if (1) class Foo { constructor() { } }", SyntaxError); + + /* EOF */ + // Clipped classes should throw a syntax error + assertError("class Foo {", SyntaxError); + assertError("class Foo {;", SyntaxError); + assertError("class Foo { constructor", SyntaxError); + assertError("class Foo { constructor(", SyntaxError); + assertError("class Foo { constructor()", SyntaxError); + assertError("class Foo { constructor()", SyntaxError); + assertError("class Foo { constructor() {", SyntaxError); + assertError("class Foo { constructor() { }", SyntaxError); +} + +if (classesEnabled()) + testClasses(); + + // Source location information diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 7a8781df99..510a56b246 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1695,8 +1695,6 @@ CASE(JSOP_UNUSED105) CASE(JSOP_UNUSED107) CASE(JSOP_UNUSED125) CASE(JSOP_UNUSED126) -CASE(JSOP_UNUSED146) -CASE(JSOP_UNUSED147) CASE(JSOP_UNUSED148) CASE(JSOP_BACKPATCH) CASE(JSOP_UNUSED157) @@ -3277,7 +3275,13 @@ CASE(JSOP_MUTATEPROTO) END_CASE(JSOP_MUTATEPROTO) CASE(JSOP_INITPROP) +CASE(JSOP_INITLOCKEDPROP) +CASE(JSOP_INITHIDDENPROP) { + static_assert(JSOP_INITPROP_LENGTH == JSOP_INITLOCKEDPROP_LENGTH, + "initprop and initlockedprop must be the same size"); + static_assert(JSOP_INITPROP_LENGTH == JSOP_INITHIDDENPROP_LENGTH, + "initprop and inithiddenprop must be the same size"); /* Load the property's initial value into rval. */ MOZ_ASSERT(REGS.stackDepth() >= 2); RootedValue& rval = rootValue0; @@ -3292,7 +3296,8 @@ CASE(JSOP_INITPROP) RootedId& id = rootId0; id = NameToId(name); - if (!NativeDefineProperty(cx, obj, id, rval, nullptr, nullptr, JSPROP_ENUMERATE)) + unsigned propAttrs = GetInitDataPropAttrs(JSOp(*REGS.pc)); + if (!NativeDefineProperty(cx, obj, id, rval, nullptr, nullptr, propAttrs)) goto error; REGS.sp--; @@ -4072,6 +4077,22 @@ js::RunOnceScriptPrologue(JSContext* cx, HandleScript script) return true; } +unsigned +js::GetInitDataPropAttrs(JSOp op) +{ + switch (op) { + case JSOP_INITPROP: + return JSPROP_ENUMERATE; + case JSOP_INITLOCKEDPROP: + return JSPROP_PERMANENT | JSPROP_READONLY; + case JSOP_INITHIDDENPROP: + // Non-enumerable, but writable and configurable + return 0; + default:; + } + MOZ_CRASH("Unknown data initprop"); +} + bool js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleId id, HandleObject val) diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 15d32dd6cb..e74e3fc102 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -376,6 +376,9 @@ bool InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleId id, HandleObject val); +unsigned +GetInitDataPropAttrs(JSOp op); + bool InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandlePropertyName name, HandleObject val); diff --git a/js/src/vm/Keywords.h b/js/src/vm/Keywords.h index 774cb42213..b544922d8e 100644 --- a/js/src/vm/Keywords.h +++ b/js/src/vm/Keywords.h @@ -43,8 +43,8 @@ macro(with, with, TOK_WITH, JSVERSION_DEFAULT) \ macro(import, import, TOK_IMPORT, JSVERSION_DEFAULT) \ macro(export, export, TOK_EXPORT, JSVERSION_DEFAULT) \ + macro(class, class_, TOK_CLASS, JSVERSION_DEFAULT) \ /* Reserved keywords. */ \ - macro(class, class_, TOK_RESERVED, JSVERSION_DEFAULT) \ macro(enum, enum_, TOK_RESERVED, JSVERSION_DEFAULT) \ macro(extends, extends, TOK_RESERVED, JSVERSION_DEFAULT) \ macro(super, super, TOK_RESERVED, JSVERSION_DEFAULT) \ diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 528f4e7794..3ac84cdfb1 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1406,10 +1406,29 @@ * Stack: => intrinsicHolder */ \ macro(JSOP_BINDINTRINSIC, 145, "bindintrinsic", NULL, 5, 0, 1, JOF_ATOM|JOF_NAME|JOF_SET) \ - \ + /* + * Initialize a non-configurable, non-writable, non-enumerable data-property on an object. + * + * Pops the top two values on the stack as 'val' and 'obj', defines + * 'nameIndex' property of 'obj' as 'val', pushes 'obj' onto the stack. + * Category: Literals + * Type: Object + * Operands: uint32_t nameIndex + * Stack: obj, val => obj + */ \ + macro(JSOP_INITLOCKEDPROP, 146, "initlockedprop", NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) \ + /* + * Initialize a non-enumerable data-property on an object. + * + * Pops the top two values on the stack as 'val' and 'obj', defines + * 'nameIndex' property of 'obj' as 'val', pushes 'obj' onto the stack. + * Category: Literals + * Type: Object + * Operands: uint32_t nameIndex + * Stack: obj, val => obj + */ \ + macro(JSOP_INITHIDDENPROP, 147,"inithiddenprop", NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) \ /* Unused. */ \ - macro(JSOP_UNUSED146, 146,"unused146", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED147, 147,"unused147", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED148, 148,"unused148", NULL, 1, 0, 0, JOF_BYTE) \ \ /* diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index c7a350e504..10fbf6f085 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,11 +29,11 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 251; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 237; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 365, +static_assert(JSErr_Limit == 371, "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 "