diff options
Diffstat (limited to 'js/src/frontend')
-rw-r--r-- | js/src/frontend/BytecodeCompiler.cpp | 25 | ||||
-rw-r--r-- | js/src/frontend/BytecodeEmitter.cpp | 436 | ||||
-rw-r--r-- | js/src/frontend/BytecodeEmitter.h | 38 | ||||
-rw-r--r-- | js/src/frontend/FoldConstants.cpp | 11 | ||||
-rw-r--r-- | js/src/frontend/FullParseHandler.h | 51 | ||||
-rw-r--r-- | js/src/frontend/GenerateReservedWords.py | 213 | ||||
-rw-r--r-- | js/src/frontend/NameAnalysisTypes.h | 14 | ||||
-rw-r--r-- | js/src/frontend/NameFunctions.cpp | 6 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.cpp | 2 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.h | 23 | ||||
-rw-r--r-- | js/src/frontend/Parser.cpp | 3145 | ||||
-rw-r--r-- | js/src/frontend/Parser.h | 477 | ||||
-rw-r--r-- | js/src/frontend/ReservedWords.h | 81 | ||||
-rw-r--r-- | js/src/frontend/SharedContext.h | 22 | ||||
-rw-r--r-- | js/src/frontend/SourceNotes.h | 3 | ||||
-rw-r--r-- | js/src/frontend/SyntaxParseHandler.h | 53 | ||||
-rw-r--r-- | js/src/frontend/TokenKind.h | 99 | ||||
-rw-r--r-- | js/src/frontend/TokenStream.cpp | 478 | ||||
-rw-r--r-- | js/src/frontend/TokenStream.h | 254 |
19 files changed, 3540 insertions, 1891 deletions
diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index b5be5f5ac..a1abbfeda 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -77,7 +77,13 @@ class MOZ_STACK_CLASS BytecodeCompiler bool canLazilyParse(); bool createParser(); bool createSourceAndParser(Maybe<uint32_t> parameterListEnd = Nothing()); - bool createScript(uint32_t preludeStart = 0); + + // If toString{Start,End} are not explicitly passed, assume the script's + // offsets in the source used to parse it are the same as what should be + // used to compute its Function.prototype.toString() value. + bool createScript(); + bool createScript(uint32_t toStringStart, uint32_t toStringEnd); + bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext); bool handleParseFailure(const Directives& newDirectives); bool deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment); @@ -242,11 +248,17 @@ BytecodeCompiler::createSourceAndParser(Maybe<uint32_t> parameterListEnd /* = No } bool -BytecodeCompiler::createScript(uint32_t preludeStart /* = 0 */) +BytecodeCompiler::createScript() +{ + return createScript(0, sourceBuffer.length()); +} + +bool +BytecodeCompiler::createScript(uint32_t toStringStart, uint32_t toStringEnd) { script = JSScript::Create(cx, options, sourceObject, /* sourceStart = */ 0, sourceBuffer.length(), - preludeStart); + toStringStart, toStringEnd); return script != nullptr; } @@ -287,7 +299,8 @@ BytecodeCompiler::deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObj RootedObject env(cx, environment); while (env->is<EnvironmentObject>() || env->is<DebugEnvironmentProxy>()) { if (env->is<CallObject>()) { - RootedScript script(cx, env->as<CallObject>().callee().getOrCreateScript(cx)); + RootedFunction fun(cx, &env->as<CallObject>().callee()); + RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun)); if (!script) return false; if (script->argumentsHasVarBinding()) { @@ -457,7 +470,7 @@ BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun, if (fn->pn_funbox->function()->isInterpreted()) { MOZ_ASSERT(fun == fn->pn_funbox->function()); - if (!createScript(fn->pn_funbox->preludeStart)) + if (!createScript(fn->pn_funbox->toStringStart, fn->pn_funbox->toStringEnd)) return false; Maybe<BytecodeEmitter> emitter; @@ -652,7 +665,7 @@ frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const cha Rooted<JSScript*> script(cx, JSScript::Create(cx, options, sourceObject, lazy->begin(), lazy->end(), - lazy->preludeStart())); + lazy->toStringStart(), lazy->toStringEnd())); if (!script) return false; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index c524184d6..309d6c290 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -3069,6 +3069,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_TRUE: case PNK_FALSE: case PNK_NULL: + case PNK_RAW_UNDEFINED: case PNK_ELISION: case PNK_GENERATOR: case PNK_NUMBER: @@ -3538,9 +3539,10 @@ BytecodeEmitter::needsImplicitThis() bool BytecodeEmitter::maybeSetDisplayURL() { - if (tokenStream()->hasDisplayURL()) { - if (!parser->ss->setDisplayURL(cx, tokenStream()->displayURL())) + if (tokenStream().hasDisplayURL()) { + if (!parser->ss->setDisplayURL(cx, tokenStream().displayURL())) { return false; + } } return true; } @@ -3548,10 +3550,11 @@ BytecodeEmitter::maybeSetDisplayURL() bool BytecodeEmitter::maybeSetSourceMap() { - if (tokenStream()->hasSourceMapURL()) { + if (tokenStream().hasSourceMapURL()) { MOZ_ASSERT(!parser->ss->hasSourceMapURL()); - if (!parser->ss->setSourceMapURL(cx, tokenStream()->sourceMapURL())) + if (!parser->ss->setSourceMapURL(cx, tokenStream().sourceMapURL())) { return false; + } } /* @@ -3590,21 +3593,21 @@ BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext* cx) } } -inline TokenStream* +inline TokenStream& BytecodeEmitter::tokenStream() { - return &parser->tokenStream; + return parser->tokenStream; } bool BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) { - TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; + TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos; va_list args; va_start(args, errorNumber); - bool result = tokenStream()->reportCompileErrorNumberVA(pos.begin, JSREPORT_ERROR, - errorNumber, args); + bool result = tokenStream().reportCompileErrorNumberVA(nullptr, pos.begin, JSREPORT_ERROR, + errorNumber, args); va_end(args); return result; } @@ -3612,11 +3615,12 @@ BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) bool BytecodeEmitter::reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...) { - TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; + TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos; va_list args; va_start(args, errorNumber); - bool result = tokenStream()->reportExtraWarningErrorNumberVA(pos.begin, errorNumber, args); + bool result = tokenStream().reportExtraWarningErrorNumberVA(nullptr, pos.begin, + errorNumber, args); va_end(args); return result; } @@ -3624,12 +3628,12 @@ BytecodeEmitter::reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...) bool BytecodeEmitter::reportStrictModeError(ParseNode* pn, unsigned errorNumber, ...) { - TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; + TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos; va_list args; va_start(args, errorNumber); - bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict(), - errorNumber, args); + bool result = tokenStream().reportStrictModeErrorNumberVA(nullptr, pos.begin, sc->strict(), + errorNumber, args); va_end(args); return result; } @@ -4599,7 +4603,7 @@ BytecodeEmitter::emitSwitch(ParseNode* pn) // If the expression is a literal, suppress line number emission so // that debugging works more naturally. if (caseValue) { - if (!emitTree(caseValue, + if (!emitTree(caseValue, ValueUsage::WantValue, caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE)) { return false; @@ -5810,36 +5814,78 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla if (!emitRequireObjectCoercible()) // ... RHS return false; + bool needsRestPropertyExcludedSet = pattern->pn_count > 1 && + pattern->last()->isKind(PNK_SPREAD); + if (needsRestPropertyExcludedSet) { + if (!emitDestructuringObjRestExclusionSet(pattern)) // ... RHS SET + return false; + + if (!emit1(JSOP_SWAP)) // ... SET RHS + return false; + } + for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { ParseNode* subpattern; - if (member->isKind(PNK_MUTATEPROTO)) + if (member->isKind(PNK_MUTATEPROTO) || member->isKind(PNK_SPREAD)) subpattern = member->pn_kid; else subpattern = member->pn_right; + ParseNode* lhs = subpattern; + MOZ_ASSERT_IF(member->isKind(PNK_SPREAD), !lhs->isKind(PNK_ASSIGN)); if (lhs->isKind(PNK_ASSIGN)) lhs = lhs->pn_left; size_t emitted; - if (!emitDestructuringLHSRef(lhs, &emitted)) // ... RHS *LREF + if (!emitDestructuringLHSRef(lhs, &emitted)) // ... *SET RHS *LREF return false; // Duplicate the value being destructured to use as a reference base. if (emitted) { - if (!emitDupAt(emitted)) // ... RHS *LREF RHS + if (!emitDupAt(emitted)) // ... *SET RHS *LREF RHS return false; } else { - if (!emit1(JSOP_DUP)) // ... RHS RHS + if (!emit1(JSOP_DUP)) // ... *SET RHS RHS return false; } + if (member->isKind(PNK_SPREAD)) { + if (!updateSourceCoordNotes(member->pn_pos.begin)) + return false; + + if (!emitNewInit(JSProto_Object)) // ... *SET RHS *LREF RHS TARGET + return false; + if (!emit1(JSOP_DUP)) // ... *SET RHS *LREF RHS TARGET TARGET + return false; + if (!emit2(JSOP_PICK, 2)) // ... *SET RHS *LREF TARGET TARGET RHS + return false; + + if (needsRestPropertyExcludedSet) { + if (!emit2(JSOP_PICK, emitted + 4)) // ... RHS *LREF TARGET TARGET RHS SET + return false; + } + + CopyOption option = needsRestPropertyExcludedSet + ? CopyOption::Filtered + : CopyOption::Unfiltered; + if (!emitCopyDataProperties(option)) // ... RHS *LREF TARGET + return false; + + // Destructure TARGET per this member's lhs. + if (!emitSetOrInitializeDestructuring(lhs, flav)) // ... RHS + return false; + + MOZ_ASSERT(member == pattern->last(), "Rest property is always last"); + break; + } + // Now push the property name currently being matched, which is the // current property name "label" on the left of a colon in the object // initialiser. bool needsGetElem = true; if (member->isKind(PNK_MUTATEPROTO)) { - if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS *LREF PROP + if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... *SET RHS *LREF PROP return false; needsGetElem = false; } else { @@ -5847,40 +5893,131 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla ParseNode* key = member->pn_left; if (key->isKind(PNK_NUMBER)) { - if (!emitNumberOp(key->pn_dval)) // ... RHS *LREF RHS KEY + if (!emitNumberOp(key->pn_dval)) // ... *SET RHS *LREF RHS KEY return false; } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { - PropertyName* name = key->pn_atom->asPropertyName(); - - // The parser already checked for atoms representing indexes and - // used PNK_NUMBER instead, but also watch for ids which TI treats - // as indexes for simplification of downstream analysis. - jsid id = NameToId(name); - if (id != IdToTypeId(id)) { - if (!emitTree(key)) // ... RHS *LREF RHS KEY + if (!emitAtomOp(key->pn_atom, JSOP_GETPROP)) // ... *SET RHS *LREF PROP + return false; + needsGetElem = false; + } else { + if (!emitComputedPropertyName(key)) // ... *SET RHS *LREF RHS KEY + return false; + + // Add the computed property key to the exclusion set. + if (needsRestPropertyExcludedSet) { + if (!emitDupAt(emitted + 3)) // ... SET RHS *LREF RHS KEY SET return false; - } else { - if (!emitAtomOp(name, JSOP_GETPROP)) // ... RHS *LREF PROP + if (!emitDupAt(1)) // ... SET RHS *LREF RHS KEY SET KEY + return false; + if (!emit1(JSOP_UNDEFINED)) // ... SET RHS *LREF RHS KEY SET KEY UNDEFINED + return false; + if (!emit1(JSOP_INITELEM)) // ... SET RHS *LREF RHS KEY SET + return false; + if (!emit1(JSOP_POP)) // ... SET RHS *LREF RHS KEY return false; - needsGetElem = false; } - } else { - if (!emitComputedPropertyName(key)) // ... RHS *LREF RHS KEY - return false; } } // Get the property value if not done already. - if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS *LREF PROP + if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... *SET RHS *LREF PROP return false; if (subpattern->isKind(PNK_ASSIGN)) { - if (!emitDefault(subpattern->pn_right, lhs)) // ... RHS *LREF VALUE + if (!emitDefault(subpattern->pn_right, lhs)) // ... *SET RHS *LREF VALUE return false; } // Destructure PROP per this member's lhs. - if (!emitSetOrInitializeDestructuring(subpattern, flav)) // ... RHS + if (!emitSetOrInitializeDestructuring(subpattern, flav)) // ... *SET RHS + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitDestructuringObjRestExclusionSet(ParseNode* pattern) +{ + MOZ_ASSERT(pattern->isKind(PNK_OBJECT)); + MOZ_ASSERT(pattern->isArity(PN_LIST)); + MOZ_ASSERT(pattern->last()->isKind(PNK_SPREAD)); + + ptrdiff_t offset = this->offset(); + if (!emitNewInit(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. + // In the case of computed property names and indices, we cannot fix the + // shape at bytecode compile time. When the shape cannot be determined, + // |obj| is nulled out. + + // No need to do any guessing for the object kind, since we know the upper + // bound of how many properties we plan to have. + gc::AllocKind kind = gc::GetGCObjectKind(pattern->pn_count - 1); + RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject)); + if (!obj) + return false; + + RootedAtom pnatom(cx); + for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { + if (member->isKind(PNK_SPREAD)) + break; + + bool isIndex = false; + if (member->isKind(PNK_MUTATEPROTO)) { + pnatom.set(cx->names().proto); + } else { + ParseNode* key = member->pn_left; + if (key->isKind(PNK_NUMBER)) { + if (!emitNumberOp(key->pn_dval)) + return false; + isIndex = true; + } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { + pnatom.set(key->pn_atom); + } else { + // Otherwise this is a computed property name which needs to + // be added dynamically. + obj.set(nullptr); + continue; + } + } + + // Initialize elements with |undefined|. + if (!emit1(JSOP_UNDEFINED)) + return false; + + if (isIndex) { + obj.set(nullptr); + if (!emit1(JSOP_INITELEM)) + return false; + } else { + uint32_t index; + if (!makeAtomIndex(pnatom, &index)) + return false; + + if (obj) { + MOZ_ASSERT(!obj->inDictionaryMode()); + Rooted<jsid> id(cx, AtomToId(pnatom)); + if (!NativeDefineProperty(cx, obj, id, UndefinedHandleValue, nullptr, nullptr, + JSPROP_ENUMERATE)) + { + return false; + } + if (obj->inDictionaryMode()) + obj.set(nullptr); + } + + if (!emitIndex32(JSOP_INITPROP, index)) + return false; + } + } + + if (obj) { + // The object survived and has a predictable shape: update the + // original bytecode. + if (!replaceNewInitWithNewObject(obj, offset)) return false; } @@ -6242,6 +6379,9 @@ ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObje case PNK_NULL: vp.setNull(); return true; + case PNK_RAW_UNDEFINED: + vp.setUndefined(); + return true; case PNK_CALLSITEOBJ: case PNK_ARRAY: { unsigned count; @@ -6281,8 +6421,8 @@ ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObje } MOZ_ASSERT(idx == count); - JSObject* obj = ObjectGroup::newArrayObject(cx, values.begin(), values.length(), - newKind, arrayKind); + ArrayObject* obj = ObjectGroup::newArrayObject(cx, values.begin(), values.length(), + newKind, arrayKind); if (!obj) return false; @@ -6645,7 +6785,7 @@ BytecodeEmitter::emitLexicalScopeBody(ParseNode* body, EmitLineNumberNote emitLi } // Line notes were updated by emitLexicalScope. - return emitTree(body, emitLineNote); + return emitTree(body, ValueUsage::WantValue, emitLineNote); } // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See @@ -6747,9 +6887,9 @@ BytecodeEmitter::emitRequireObjectCoercible() return false; if (!emit2(JSOP_PICK, 2)) // VAL REQUIREOBJECTCOERCIBLE UNDEFINED VAL return false; - if (!emitCall(JSOP_CALL, 1)) // VAL IGNORED + if (!emitCall(JSOP_CALL_IGNORES_RV, 1))// VAL IGNORED return false; - checkTypeSet(JSOP_CALL); + checkTypeSet(JSOP_CALL_IGNORES_RV); if (!emit1(JSOP_POP)) // VAL return false; @@ -6759,6 +6899,53 @@ BytecodeEmitter::emitRequireObjectCoercible() } bool +BytecodeEmitter::emitCopyDataProperties(CopyOption option) +{ + DebugOnly<int32_t> depth = this->stackDepth; + + uint32_t argc; + if (option == CopyOption::Filtered) { + MOZ_ASSERT(depth > 2); // TARGET SOURCE SET + argc = 3; + + if (!emitAtomOp(cx->names().CopyDataProperties, + JSOP_GETINTRINSIC)) // TARGET SOURCE SET COPYDATAPROPERTIES + { + return false; + } + } else { + MOZ_ASSERT(depth > 1); // TARGET SOURCE + argc = 2; + + if (!emitAtomOp(cx->names().CopyDataPropertiesUnfiltered, + JSOP_GETINTRINSIC)) // TARGET SOURCE COPYDATAPROPERTIES + { + return false; + } + } + + if (!emit1(JSOP_UNDEFINED)) // TARGET SOURCE *SET COPYDATAPROPERTIES UNDEFINED + return false; + if (!emit2(JSOP_PICK, argc + 1)) // SOURCE *SET COPYDATAPROPERTIES UNDEFINED TARGET + return false; + if (!emit2(JSOP_PICK, argc + 1)) // *SET COPYDATAPROPERTIES UNDEFINED TARGET SOURCE + return false; + if (option == CopyOption::Filtered) { + if (!emit2(JSOP_PICK, argc + 1)) // COPYDATAPROPERTIES UNDEFINED TARGET SOURCE SET + return false; + } + if (!emitCall(JSOP_CALL_IGNORES_RV, argc)) // IGNORED + return false; + checkTypeSet(JSOP_CALL_IGNORES_RV); + + if (!emit1(JSOP_POP)) // - + return false; + + MOZ_ASSERT(depth - int(argc) == this->stackDepth); + return true; +} + +bool BytecodeEmitter::emitIterator() { // Convert iterable to iterator. @@ -7269,12 +7456,14 @@ BytecodeEmitter::emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterSc // scope, but we still need to emit code for the initializers.) if (!updateSourceCoordNotes(init->pn_pos.begin)) return false; - if (!emitTree(init)) - return false; - - if (!init->isForLoopDeclaration()) { + if (init->isForLoopDeclaration()) { + if (!emitTree(init)) + return false; + } else { // 'init' is an expression, not a declaration. emitTree left its // value on the stack. + if (!emitTree(init, ValueUsage::IgnoreValue)) + return false; if (!emit1(JSOP_POP)) return false; } @@ -7356,7 +7545,7 @@ BytecodeEmitter::emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterSc if (!updateSourceCoordNotes(update->pn_pos.begin)) return false; - if (!emitTree(update)) + if (!emitTree(update, ValueUsage::IgnoreValue)) return false; if (!emit1(JSOP_POP)) return false; @@ -7839,7 +8028,8 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) Rooted<JSObject*> sourceObject(cx, script->sourceObject()); Rooted<JSScript*> script(cx, JSScript::Create(cx, options, sourceObject, funbox->bufStart, funbox->bufEnd, - funbox->preludeStart)); + funbox->toStringStart, + funbox->toStringEnd)); if (!script) return false; @@ -8682,8 +8872,9 @@ BytecodeEmitter::emitStatement(ParseNode* pn) if (useful) { JSOp op = wantval ? JSOP_SETRVAL : JSOP_POP; + ValueUsage valueUsage = wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue; MOZ_ASSERT_IF(pn2->isKind(PNK_ASSIGN), pn2->isOp(JSOP_NOP)); - if (!emitTree(pn2)) + if (!emitTree(pn2, valueUsage)) return false; if (!emit1(op)) return false; @@ -9033,7 +9224,7 @@ BytecodeEmitter::emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitte } bool -BytecodeEmitter::emitCallOrNew(ParseNode* pn) +BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */) { bool callop = pn->isKind(PNK_CALL) || pn->isKind(PNK_TAGGED_TEMPLATE); /* @@ -9192,7 +9383,7 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) return false; } - if (!emitArray(args, argc, JSOP_SPREADCALLARRAY)) + if (!emitArray(args, argc)) return false; if (optCodeEmitted) { @@ -9212,13 +9403,20 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) } if (!spread) { - if (!emitCall(pn->getOp(), argc, pn)) - return false; + if (pn->getOp() == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) { + if (!emitCall(JSOP_CALL_IGNORES_RV, argc, pn)) + return false; + checkTypeSet(JSOP_CALL_IGNORES_RV); + } else { + if (!emitCall(pn->getOp(), argc, pn)) + return false; + checkTypeSet(pn->getOp()); + } } else { if (!emit1(pn->getOp())) return false; + checkTypeSet(pn->getOp()); } - checkTypeSet(pn->getOp()); if (pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_STRICTEVAL) || pn->isOp(JSOP_SPREADEVAL) || @@ -9316,12 +9514,13 @@ BytecodeEmitter::emitLogical(ParseNode* pn) } bool -BytecodeEmitter::emitSequenceExpr(ParseNode* pn) +BytecodeEmitter::emitSequenceExpr(ParseNode* pn, + ValueUsage valueUsage /* = ValueUsage::WantValue */) { for (ParseNode* child = pn->pn_head; ; child = child->pn_next) { if (!updateSourceCoordNotes(child->pn_pos.begin)) return false; - if (!emitTree(child)) + if (!emitTree(child, child->pn_next ? ValueUsage::IgnoreValue : valueUsage)) return false; if (!child->pn_next) break; @@ -9384,7 +9583,8 @@ BytecodeEmitter::emitLabeledStatement(const LabeledStatement* pn) } bool -BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional) +BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional, + ValueUsage valueUsage /* = ValueUsage::WantValue */) { /* Emit the condition, then branch if false to the else part. */ if (!emitTree(&conditional.condition())) @@ -9394,13 +9594,13 @@ BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional) if (!ifThenElse.emitCond()) return false; - if (!emitTreeInBranch(&conditional.thenExpression())) + if (!emitTreeInBranch(&conditional.thenExpression(), valueUsage)) return false; if (!ifThenElse.emitElse()) return false; - if (!emitTreeInBranch(&conditional.elseExpression())) + if (!emitTreeInBranch(&conditional.elseExpression(), valueUsage)) return false; if (!ifThenElse.emitEnd()) @@ -9429,6 +9629,22 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, continue; } + if (propdef->isKind(PNK_SPREAD)) { + MOZ_ASSERT(type == ObjectLiteral); + + if (!emit1(JSOP_DUP)) + return false; + + if (!emitTree(propdef->pn_kid)) + return false; + + if (!emitCopyDataProperties(CopyOption::Unfiltered)) + return false; + + objp.set(nullptr); + continue; + } + bool extraPop = false; if (type == ClassBody && propdef->as<ClassMethod>().isStatic()) { extraPop = true; @@ -9452,16 +9668,6 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, { continue; } - - // The parser already checked for atoms representing indexes and - // used PNK_NUMBER instead, but also watch for ids which TI treats - // as indexes for simpliciation of downstream analysis. - jsid id = NameToId(key->pn_atom->asPropertyName()); - if (id != IdToTypeId(id)) { - if (!emitTree(key)) - return false; - isIndex = true; - } } else { if (!emitComputedPropertyName(key)) return false; @@ -9542,8 +9748,7 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, MOZ_ASSERT(!IsHiddenInitOp(op)); MOZ_ASSERT(!objp->inDictionaryMode()); Rooted<jsid> id(cx, AtomToId(key->pn_atom)); - RootedValue undefinedValue(cx, UndefinedValue()); - if (!NativeDefineProperty(cx, objp, id, undefinedValue, nullptr, nullptr, + if (!NativeDefineProperty(cx, objp, id, UndefinedHandleValue, nullptr, nullptr, JSPROP_ENUMERATE)) { return false; @@ -9586,15 +9791,16 @@ BytecodeEmitter::emitObject(ParseNode* pn) if (!emitNewInit(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); - // No need to do any guessing for the object kind, since we know exactly - // how many properties we plan to have. + // Try to construct the shape of the object as we go, so we can emit a + // JSOP_NEWOBJECT with the final shape instead. + // In the case of computed property names and indices, we cannot fix the + // shape at bytecode compile time. When the shape cannot be determined, + // |obj| is nulled out. + + // No need to do any guessing for the object kind, since we know the upper + // bound of how many properties we plan to have. gc::AllocKind kind = gc::GetGCObjectKind(pn->pn_count); - obj = NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject); + RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject)); if (!obj) return false; @@ -9602,25 +9808,34 @@ BytecodeEmitter::emitObject(ParseNode* pn) return false; if (obj) { - /* - * The object survived and has a predictable shape: update the original - * bytecode. - */ - ObjectBox* objbox = parser->newObjectBox(obj); - if (!objbox) + // The object survived and has a predictable shape: update the original + // bytecode. + if (!replaceNewInitWithNewObject(obj, offset)) return false; + } - static_assert(JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH, - "newinit and newobject must have equal length to edit in-place"); + return true; +} - uint32_t index = objectList.add(objbox); - jsbytecode* code = this->code(offset); - code[0] = JSOP_NEWOBJECT; - code[1] = jsbytecode(index >> 24); - code[2] = jsbytecode(index >> 16); - code[3] = jsbytecode(index >> 8); - code[4] = jsbytecode(index); - } +bool +BytecodeEmitter::replaceNewInitWithNewObject(JSObject* obj, ptrdiff_t offset) +{ + ObjectBox* objbox = parser->newObjectBox(obj); + if (!objbox) + return false; + + static_assert(JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH, + "newinit and newobject must have equal length to edit in-place"); + + uint32_t index = objectList.add(objbox); + jsbytecode* code = this->code(offset); + + MOZ_ASSERT(code[0] == JSOP_NEWINIT); + code[0] = JSOP_NEWOBJECT; + code[1] = jsbytecode(index >> 24); + code[2] = jsbytecode(index >> 16); + code[3] = jsbytecode(index >> 8); + code[4] = jsbytecode(index); return true; } @@ -9683,11 +9898,11 @@ BytecodeEmitter::emitArrayLiteral(ParseNode* pn) } } - return emitArray(pn->pn_head, pn->pn_count, JSOP_NEWARRAY); + return emitArray(pn->pn_head, pn->pn_count); } bool -BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) +BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count) { /* @@ -9698,7 +9913,6 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) * to avoid dup'ing and popping the array as each element is added, as * JSOP_SETELEM/JSOP_SETPROP would do. */ - MOZ_ASSERT(op == JSOP_NEWARRAY || op == JSOP_SPREADCALLARRAY); uint32_t nspread = 0; for (ParseNode* elt = pn; elt; elt = elt->pn_next) { @@ -9719,7 +9933,7 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) // For arrays with spread, this is a very pessimistic allocation, the // minimum possible final size. - if (!emitUint32Operand(op, count - nspread)) // ARRAY + if (!emitUint32Operand(JSOP_NEWARRAY, count - nspread)) // ARRAY return false; ParseNode* pn2 = pn; @@ -10220,6 +10434,13 @@ BytecodeEmitter::emitClass(ParseNode* pn) return false; } } else { + // In the case of default class constructors, emit the start and end + // offsets in the source buffer as source notes so that when we + // actually make the constructor during execution, we can give it the + // correct toString output. + if (!newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(pn->pn_pos.begin), ptrdiff_t(pn->pn_pos.end))) + return false; + JSAtom *name = names ? names->innerBinding()->pn_atom : cx->names().empty; if (heritageExpression) { if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) @@ -10274,7 +10495,8 @@ BytecodeEmitter::emitClass(ParseNode* pn) } bool -BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) +BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */, + EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { JS_CHECK_RECURSION(cx, return false); @@ -10396,7 +10618,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) break; case PNK_COMMA: - if (!emitSequenceExpr(pn)) + if (!emitSequenceExpr(pn, valueUsage)) return false; break; @@ -10418,7 +10640,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) break; case PNK_CONDITIONAL: - if (!emitConditionalExpression(pn->as<ConditionalExpression>())) + if (!emitConditionalExpression(pn->as<ConditionalExpression>(), valueUsage)) return false; break; @@ -10531,7 +10753,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) case PNK_CALL: case PNK_GENEXP: case PNK_SUPERCALL: - if (!emitCallOrNew(pn)) + if (!emitCallOrNew(pn, valueUsage)) return false; break; @@ -10637,6 +10859,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) case PNK_TRUE: case PNK_FALSE: case PNK_NULL: + case PNK_RAW_UNDEFINED: if (!emit1(pn->getOp())) return false; break; @@ -10688,12 +10911,13 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) } bool -BytecodeEmitter::emitTreeInBranch(ParseNode* pn) +BytecodeEmitter::emitTreeInBranch(ParseNode* pn, + ValueUsage valueUsage /* = ValueUsage::WantValue */) { // Code that may be conditionally executed always need their own TDZ // cache. TDZCheckCache tdzCache(this); - return emitTree(pn); + return emitTree(pn, valueUsage); } static bool diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 814fa11d4..595ee6405 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -171,6 +171,11 @@ struct JumpList { void patchAll(jsbytecode* code, JumpTarget target); }; +enum class ValueUsage { + WantValue, + IgnoreValue +}; + struct MOZ_STACK_CLASS BytecodeEmitter { class TDZCheckCache; @@ -356,7 +361,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool maybeSetSourceMap(); void tellDebuggerAboutCompiledScript(ExclusiveContext* cx); - inline TokenStream* tokenStream(); + inline TokenStream& tokenStream(); BytecodeVector& code() const { return current->code; } jsbytecode* code(ptrdiff_t offset) const { return current->code.begin() + offset; } @@ -434,10 +439,12 @@ struct MOZ_STACK_CLASS BytecodeEmitter }; // Emit code for the tree rooted at pn. - MOZ_MUST_USE bool emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); + MOZ_MUST_USE bool emitTree(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue, + EmitLineNumberNote emitLineNote = EMIT_LINENOTE); // Emit code for the tree rooted at pn with its own TDZ cache. - MOZ_MUST_USE bool emitTreeInBranch(ParseNode* pn); + MOZ_MUST_USE bool emitTreeInBranch(ParseNode* pn, + ValueUsage valueUsage = ValueUsage::WantValue); // Emit global, eval, or module code for tree rooted at body. Always // encompasses the entire source. @@ -521,7 +528,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitAtomOp(ParseNode* pn, JSOp op); MOZ_MUST_USE bool emitArrayLiteral(ParseNode* pn); - MOZ_MUST_USE bool emitArray(ParseNode* pn, uint32_t count, JSOp op); + MOZ_MUST_USE bool emitArray(ParseNode* pn, uint32_t count); MOZ_MUST_USE bool emitArrayComp(ParseNode* pn); MOZ_MUST_USE bool emitInternedScopeOp(uint32_t index, JSOp op); @@ -533,6 +540,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction(ParseNode* pn, bool needsProto = false); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ParseNode* pn); + MOZ_MUST_USE bool replaceNewInitWithNewObject(JSObject* obj, ptrdiff_t offset); + MOZ_MUST_USE bool emitHoistedFunctionsInList(ParseNode* pn); MOZ_MUST_USE bool emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, @@ -660,6 +669,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter // []/{} expression). MOZ_MUST_USE bool emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav); + // emitDestructuringObjRestExclusionSet emits the property exclusion set + // for the rest-property in an object pattern. + MOZ_MUST_USE bool emitDestructuringObjRestExclusionSet(ParseNode* pattern); + // emitDestructuringOps assumes the to-be-destructured value has been // pushed on the stack and emits code to destructure each part of a [] or // {} lhs expression. @@ -677,6 +690,15 @@ struct MOZ_STACK_CLASS BytecodeEmitter // object, with no overall effect on the stack. MOZ_MUST_USE bool emitRequireObjectCoercible(); + enum class CopyOption { + Filtered, Unfiltered + }; + + // Calls either the |CopyDataProperties| or the + // |CopyDataPropertiesUnfiltered| intrinsic function, consumes three (or + // two in the latter case) elements from the stack. + MOZ_MUST_USE bool emitCopyDataProperties(CopyOption option); + // emitIterator expects the iterable to already be on the stack. // It will replace that stack value with the corresponding iterator MOZ_MUST_USE bool emitIterator(); @@ -724,16 +746,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitRightAssociative(ParseNode* pn); MOZ_MUST_USE bool emitLeftAssociative(ParseNode* pn); MOZ_MUST_USE bool emitLogical(ParseNode* pn); - MOZ_MUST_USE bool emitSequenceExpr(ParseNode* pn); + MOZ_MUST_USE bool emitSequenceExpr(ParseNode* pn, + ValueUsage valueUsage = ValueUsage::WantValue); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitIncOrDec(ParseNode* pn); - MOZ_MUST_USE bool emitConditionalExpression(ConditionalExpression& conditional); + MOZ_MUST_USE bool emitConditionalExpression(ConditionalExpression& conditional, + ValueUsage valueUsage = ValueUsage::WantValue); MOZ_MUST_USE bool isRestParameter(ParseNode* pn, bool* result); MOZ_MUST_USE bool emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitted); - MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn); + MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue); MOZ_MUST_USE bool emitSelfHostedCallFunction(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedResumeGenerator(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedForceInterpreter(ParseNode* pn); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 6f62ffac6..689fa02b4 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -378,6 +378,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_TRUE: case PNK_FALSE: case PNK_NULL: + case PNK_RAW_UNDEFINED: case PNK_THIS: case PNK_ELISION: case PNK_NUMBER: @@ -468,6 +469,7 @@ IsEffectless(ParseNode* node) node->isKind(PNK_TEMPLATE_STRING) || node->isKind(PNK_NUMBER) || node->isKind(PNK_NULL) || + node->isKind(PNK_RAW_UNDEFINED) || node->isKind(PNK_FUNCTION) || node->isKind(PNK_GENEXP); } @@ -492,6 +494,7 @@ Boolish(ParseNode* pn) case PNK_FALSE: case PNK_NULL: + case PNK_RAW_UNDEFINED: return Falsy; case PNK_VOID: { @@ -1342,15 +1345,8 @@ FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& if (!name) return true; - // Also don't optimize if the name doesn't map directly to its id for TI's - // purposes. - if (NameToId(name) != IdToTypeId(NameToId(name))) - return true; - // Optimization 3: We have expr["foo"] where foo is not an index. Convert // to a property access (like expr.foo) that optimizes better downstream. - // Don't bother with this for names that TI considers to be indexes, to - // simplify downstream analysis. ParseNode* dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end); if (!dottedAccess) return false; @@ -1643,6 +1639,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo case PNK_TRUE: case PNK_FALSE: case PNK_NULL: + case PNK_RAW_UNDEFINED: case PNK_ELISION: case PNK_NUMBER: case PNK_DEBUGGER: diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index b619cf24c..2d7f57e1e 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -10,6 +10,8 @@ #include "mozilla/Attributes.h" #include "mozilla/PodOperations.h" +#include <string.h> + #include "frontend/ParseNode.h" #include "frontend/SharedContext.h" @@ -181,6 +183,10 @@ class FullParseHandler return new_<NullLiteral>(pos); } + ParseNode* newRawUndefinedLiteral(const TokenPos& pos) { + return new_<RawUndefinedLiteral>(pos); + } + // The Boxer object here is any object that can allocate ObjectBoxes. // Specifically, a Boxer has a .newObjectBox(T) method that accepts a // Rooted<RegExpObject*> argument and returns an ObjectBox*. @@ -296,10 +302,9 @@ class FullParseHandler } MOZ_MUST_USE bool addSpreadElement(ParseNode* literal, uint32_t begin, ParseNode* inner) { - TokenPos pos(begin, inner->pn_pos.end); - ParseNode* spread = new_<UnaryNode>(PNK_SPREAD, JSOP_NOP, pos, inner); + ParseNode* spread = newSpread(begin, inner); if (!spread) - return null(); + return false; literal->append(spread); literal->pn_xflags |= PNX_ARRAYHOLESPREAD | PNX_NONCONST; return true; @@ -327,8 +332,10 @@ class FullParseHandler return literal; } - ParseNode* newClass(ParseNode* name, ParseNode* heritage, ParseNode* methodBlock) { - return new_<ClassNode>(name, heritage, methodBlock); + ParseNode* newClass(ParseNode* name, ParseNode* heritage, ParseNode* methodBlock, + const TokenPos& pos) + { + return new_<ClassNode>(name, heritage, methodBlock, pos); } ParseNode* newClassMethodList(uint32_t begin) { return new_<ListNode>(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1)); @@ -388,6 +395,18 @@ class FullParseHandler return true; } + MOZ_MUST_USE bool addSpreadProperty(ParseNode* literal, uint32_t begin, ParseNode* inner) { + MOZ_ASSERT(literal->isKind(PNK_OBJECT)); + MOZ_ASSERT(literal->isArity(PN_LIST)); + + setListFlag(literal, PNX_NONCONST); + ParseNode* spread = newSpread(begin, inner); + if (!spread) + return false; + literal->append(spread); + return true; + } + MOZ_MUST_USE bool addObjectMethodDefinition(ParseNode* literal, ParseNode* key, ParseNode* fn, JSOp op) { @@ -870,29 +889,29 @@ class FullParseHandler return node->isKind(PNK_NAME); } - bool nameIsEvalAnyParentheses(ParseNode* node, ExclusiveContext* cx) { - MOZ_ASSERT(isNameAnyParentheses(node), - "must only call this function on known names"); + bool isArgumentsAnyParentheses(ParseNode* node, ExclusiveContext* cx) { + return node->isKind(PNK_NAME) && node->pn_atom == cx->names().arguments; + } - return node->pn_atom == cx->names().eval; + bool isEvalAnyParentheses(ParseNode* node, ExclusiveContext* cx) { + return node->isKind(PNK_NAME) && node->pn_atom == cx->names().eval; } const char* nameIsArgumentsEvalAnyParentheses(ParseNode* node, ExclusiveContext* cx) { MOZ_ASSERT(isNameAnyParentheses(node), "must only call this function on known names"); - if (nameIsEvalAnyParentheses(node, cx)) + if (isEvalAnyParentheses(node, cx)) return js_eval_str; - if (node->pn_atom == cx->names().arguments) + if (isArgumentsAnyParentheses(node, cx)) return js_arguments_str; return nullptr; } - bool nameIsUnparenthesizedAsync(ParseNode* node, ExclusiveContext* cx) { - MOZ_ASSERT(isNameAnyParentheses(node), - "must only call this function on known names"); - - return node->pn_atom == cx->names().async; + bool isAsyncKeyword(ParseNode* node, ExclusiveContext* cx) { + return node->isKind(PNK_NAME) && + node->pn_pos.begin + strlen("async") == node->pn_pos.end && + node->pn_atom == cx->names().async; } bool isCall(ParseNode* pn) { diff --git a/js/src/frontend/GenerateReservedWords.py b/js/src/frontend/GenerateReservedWords.py new file mode 100644 index 000000000..bd698cc5f --- /dev/null +++ b/js/src/frontend/GenerateReservedWords.py @@ -0,0 +1,213 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import re +import sys + +def read_reserved_word_list(filename): + macro_pat = re.compile(r"^\s*macro\(([^,]+), *[^,]+, *[^\)]+\)\s*\\?$") + + reserved_word_list = [] + index = 0 + with open(filename, 'r') as f: + for line in f: + m = macro_pat.search(line) + if m: + reserved_word_list.append((index, m.group(1))) + index += 1 + + assert(len(reserved_word_list) != 0) + + return reserved_word_list + +def line(opt, s): + opt['output'].write('{}{}\n'.format(' ' * opt['indent_level'], s)) + +def indent(opt): + opt['indent_level'] += 1 + +def dedent(opt): + opt['indent_level'] -= 1 + +def span_and_count_at(reserved_word_list, column): + assert(len(reserved_word_list) != 0); + + chars_dict = {} + for index, word in reserved_word_list: + chars_dict[ord(word[column])] = True + + chars = sorted(chars_dict.keys()) + return chars[-1] - chars[0] + 1, len(chars) + +def optimal_switch_column(opt, reserved_word_list, columns, unprocessed_columns): + assert(len(reserved_word_list) != 0); + assert(unprocessed_columns != 0); + + min_count = 0 + min_span = 0 + min_count_index = 0 + min_span_index = 0 + + for index in range(0, unprocessed_columns): + span, count = span_and_count_at(reserved_word_list, columns[index]) + assert(span != 0) + + if span == 1: + assert(count == 1) + return 1, True + + assert(count != 1) + if index == 0 or min_span > span: + min_span = span + min_span_index = index + + if index == 0 or min_count > count: + min_count = count + min_count_index = index + + if min_count <= opt['use_if_threshold']: + return min_count_index, True + + return min_span_index, False + +def split_list_per_column(reserved_word_list, column): + assert(len(reserved_word_list) != 0); + + column_dict = {} + for item in reserved_word_list: + index, word = item + per_column = column_dict.setdefault(word[column], []) + per_column.append(item) + + return sorted(column_dict.items(), key=lambda (char, word): ord(char)) + +def generate_letter_switch(opt, unprocessed_columns, reserved_word_list, + columns=None): + assert(len(reserved_word_list) != 0); + + if not columns: + columns = range(0, unprocessed_columns) + + if len(reserved_word_list) == 1: + index, word = reserved_word_list[0] + + if unprocessed_columns == 0: + line(opt, 'JSRW_GOT_MATCH({}) /* {} */'.format(index, word)) + return + + if unprocessed_columns > opt['char_tail_test_threshold']: + line(opt, 'JSRW_TEST_GUESS({}) /* {} */'.format(index, word)) + return + + conds = [] + for column in columns[0:unprocessed_columns]: + quoted = repr(word[column]) + conds.append('JSRW_AT({})=={}'.format(column, quoted)) + + line(opt, 'if ({}) {{'.format(' && '.join(conds))) + + indent(opt) + line(opt, 'JSRW_GOT_MATCH({}) /* {} */'.format(index, word)) + dedent(opt) + + line(opt, '}') + line(opt, 'JSRW_NO_MATCH()') + return + + assert(unprocessed_columns != 0); + + optimal_column_index, use_if = optimal_switch_column(opt, reserved_word_list, + columns, + unprocessed_columns) + optimal_column = columns[optimal_column_index] + + # Make a copy to avoid breaking passed list. + columns = columns[:] + columns[optimal_column_index] = columns[unprocessed_columns - 1] + + list_per_column = split_list_per_column(reserved_word_list, optimal_column) + + if not use_if: + line(opt, 'switch (JSRW_AT({})) {{'.format(optimal_column)) + + for char, reserved_word_list_per_column in list_per_column: + quoted = repr(char) + if use_if: + line(opt, 'if (JSRW_AT({}) == {}) {{'.format(optimal_column, + quoted)) + else: + line(opt, ' case {}:'.format(quoted)) + + indent(opt) + generate_letter_switch(opt, unprocessed_columns - 1, + reserved_word_list_per_column, columns) + dedent(opt) + + if use_if: + line(opt, '}') + + if not use_if: + line(opt, '}') + + line(opt, 'JSRW_NO_MATCH()') + +def split_list_per_length(reserved_word_list): + assert(len(reserved_word_list) != 0); + + length_dict = {} + for item in reserved_word_list: + index, word = item + per_length = length_dict.setdefault(len(word), []) + per_length.append(item) + + return sorted(length_dict.items(), key=lambda (length, word): length) + +def generate_switch(opt, reserved_word_list): + assert(len(reserved_word_list) != 0); + + line(opt, '/*') + line(opt, ' * Generating switch for the list of {} entries:'.format(len(reserved_word_list))) + for index, word in reserved_word_list: + line(opt, ' * {}'.format(word)) + line(opt, ' */') + + list_per_length = split_list_per_length(reserved_word_list) + + use_if = False + if len(list_per_length) < opt['use_if_threshold']: + use_if = True + + if not use_if: + line(opt, 'switch (JSRW_LENGTH()) {') + + for length, reserved_word_list_per_length in list_per_length: + if use_if: + line(opt, 'if (JSRW_LENGTH() == {}) {{'.format(length)) + else: + line(opt, ' case {}:'.format(length)) + + indent(opt) + generate_letter_switch(opt, length, reserved_word_list_per_length) + dedent(opt) + + if use_if: + line(opt, '}') + + if not use_if: + line(opt, '}') + line(opt, 'JSRW_NO_MATCH()') + +def main(output, reserved_words_h): + reserved_word_list = read_reserved_word_list(reserved_words_h) + + opt = { + 'indent_level': 1, + 'use_if_threshold': 3, + 'char_tail_test_threshold': 4, + 'output': output + } + generate_switch(opt, reserved_word_list) + +if __name__ == '__main__': + main(sys.stdout, *sys.argv[1:]) diff --git a/js/src/frontend/NameAnalysisTypes.h b/js/src/frontend/NameAnalysisTypes.h index d39e177fb..2d327c827 100644 --- a/js/src/frontend/NameAnalysisTypes.h +++ b/js/src/frontend/NameAnalysisTypes.h @@ -78,6 +78,7 @@ enum class DeclarationKind : uint8_t Const, Import, BodyLevelFunction, + ModuleBodyLevelFunction, LexicalFunction, VarForAnnexBLexicalFunction, SimpleCatchParameter, @@ -95,6 +96,7 @@ DeclarationKindToBindingKind(DeclarationKind kind) case DeclarationKind::Var: case DeclarationKind::BodyLevelFunction: + case DeclarationKind::ModuleBodyLevelFunction: case DeclarationKind::VarForAnnexBLexicalFunction: case DeclarationKind::ForOfVar: return BindingKind::Var; @@ -124,6 +126,7 @@ DeclarationKindIsLexical(DeclarationKind kind) // Used in Parser to track declared names. class DeclaredNameInfo { + uint32_t pos_; DeclarationKind kind_; // If the declared name is a binding, whether the binding is closed @@ -132,8 +135,9 @@ class DeclaredNameInfo bool closedOver_; public: - explicit DeclaredNameInfo(DeclarationKind kind) - : kind_(kind), + explicit DeclaredNameInfo(DeclarationKind kind, uint32_t pos) + : pos_(pos), + kind_(kind), closedOver_(false) { } @@ -144,6 +148,12 @@ class DeclaredNameInfo return kind_; } + static const uint32_t npos = uint32_t(-1); + + uint32_t pos() const { + return pos_; + } + void alterKind(DeclarationKind kind) { kind_ = kind; } diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index ce1318f0b..dc54d0a88 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -316,7 +316,8 @@ class NameResolver return false; // Next is the callsite object node. This node only contains - // internal strings and an array -- no user-controlled expressions. + // internal strings or undefined and an array -- no user-controlled + // expressions. element = element->pn_next; #ifdef DEBUG { @@ -326,7 +327,7 @@ class NameResolver for (ParseNode* kid = array->pn_head; kid; kid = kid->pn_next) MOZ_ASSERT(kid->isKind(PNK_TEMPLATE_STRING)); for (ParseNode* next = array->pn_next; next; next = next->pn_next) - MOZ_ASSERT(next->isKind(PNK_TEMPLATE_STRING)); + MOZ_ASSERT(next->isKind(PNK_TEMPLATE_STRING) || next->isKind(PNK_RAW_UNDEFINED)); } #endif @@ -382,6 +383,7 @@ class NameResolver case PNK_TRUE: case PNK_FALSE: case PNK_NULL: + case PNK_RAW_UNDEFINED: case PNK_ELISION: case PNK_GENERATOR: case PNK_NUMBER: diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 91f17625c..5fe64e3d3 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -190,6 +190,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_TRUE: case PNK_FALSE: case PNK_NULL: + case PNK_RAW_UNDEFINED: case PNK_ELISION: case PNK_GENERATOR: case PNK_NUMBER: @@ -685,6 +686,7 @@ NullaryNode::dump() case PNK_TRUE: fprintf(stderr, "#true"); break; case PNK_FALSE: fprintf(stderr, "#false"); break; case PNK_NULL: fprintf(stderr, "#null"); break; + case PNK_RAW_UNDEFINED: fprintf(stderr, "#undefined"); break; case PNK_NUMBER: { ToCStringBuf cbuf; diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index c3523afe4..1f20f3988 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -54,6 +54,7 @@ class ObjectBox; F(TRUE) \ F(FALSE) \ F(NULL) \ + F(RAW_UNDEFINED) \ F(THIS) \ F(FUNCTION) \ F(MODULE) \ @@ -406,7 +407,8 @@ IsTypeofKind(ParseNodeKind kind) * PNK_NUMBER dval pn_dval: double value of numeric literal * PNK_TRUE, nullary pn_op: JSOp bytecode * PNK_FALSE, - * PNK_NULL + * PNK_NULL, + * PNK_RAW_UNDEFINED * * PNK_THIS, unary pn_kid: '.this' Name if function `this`, else nullptr * PNK_SUPERBASE unary pn_kid: '.this' Name @@ -686,7 +688,8 @@ class ParseNode isKind(PNK_STRING) || isKind(PNK_TRUE) || isKind(PNK_FALSE) || - isKind(PNK_NULL); + isKind(PNK_NULL) || + isKind(PNK_RAW_UNDEFINED); } /* Return true if this node appears in a Directive Prologue. */ @@ -1141,6 +1144,16 @@ class NullLiteral : public ParseNode explicit NullLiteral(const TokenPos& pos) : ParseNode(PNK_NULL, JSOP_NULL, PN_NULLARY, pos) { } }; +// This is only used internally, currently just for tagged templates. +// It represents the value 'undefined' (aka `void 0`), like NullLiteral +// represents the value 'null'. +class RawUndefinedLiteral : public ParseNode +{ + public: + explicit RawUndefinedLiteral(const TokenPos& pos) + : ParseNode(PNK_RAW_UNDEFINED, JSOP_UNDEFINED, PN_NULLARY, pos) { } +}; + class BooleanLiteral : public ParseNode { public: @@ -1296,8 +1309,9 @@ struct ClassNames : public BinaryNode { }; struct ClassNode : public TernaryNode { - ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock) - : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodsOrBlock) + ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock, + const TokenPos& pos) + : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodsOrBlock, pos) { MOZ_ASSERT_IF(names, names->is<ClassNames>()); MOZ_ASSERT(methodsOrBlock->is<LexicalScopeNode>() || @@ -1361,6 +1375,7 @@ ParseNode::isConstant() case PNK_STRING: case PNK_TEMPLATE_STRING: case PNK_NULL: + case PNK_RAW_UNDEFINED: case PNK_FALSE: case PNK_TRUE: return true; diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 3b7a0e612..0c279591f 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -19,6 +19,8 @@ #include "frontend/Parser.h" +#include "mozilla/Sprintf.h" + #include <new> #include "jsapi.h" @@ -62,19 +64,33 @@ using AddDeclaredNamePtr = ParseContext::Scope::AddDeclaredNamePtr; using BindingIter = ParseContext::Scope::BindingIter; using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr; -/* Read a token. Report an error and return null() if that token isn't of type tt. */ -#define MUST_MATCH_TOKEN_MOD(tt, modifier, errorNumber) \ +// Read a token. Report an error and return null() if that token doesn't match +// to the condition. Do not use MUST_MATCH_TOKEN_INTERNAL directly. +#define MUST_MATCH_TOKEN_INTERNAL(cond, modifier, errorReport) \ JS_BEGIN_MACRO \ TokenKind token; \ if (!tokenStream.getToken(&token, modifier)) \ return null(); \ - if (token != tt) { \ - error(errorNumber); \ + if (!(cond)) { \ + errorReport; \ return null(); \ } \ JS_END_MACRO -#define MUST_MATCH_TOKEN(tt, errno) MUST_MATCH_TOKEN_MOD(tt, TokenStream::None, errno) +#define MUST_MATCH_TOKEN_MOD(tt, modifier, errorNumber) \ + MUST_MATCH_TOKEN_INTERNAL(token == tt, modifier, error(errorNumber)) + +#define MUST_MATCH_TOKEN(tt, errorNumber) \ + MUST_MATCH_TOKEN_MOD(tt, TokenStream::None, errorNumber) + +#define MUST_MATCH_TOKEN_FUNC_MOD(func, modifier, errorNumber) \ + MUST_MATCH_TOKEN_INTERNAL((func)(token), modifier, error(errorNumber)) + +#define MUST_MATCH_TOKEN_FUNC(func, errorNumber) \ + MUST_MATCH_TOKEN_FUNC_MOD(func, TokenStream::None, errorNumber) + +#define MUST_MATCH_TOKEN_MOD_WITH_REPORT(tt, modifier, errorReport) \ + MUST_MATCH_TOKEN_INTERNAL(token == tt, modifier, errorReport) template <class T, class U> static inline void @@ -106,6 +122,7 @@ DeclarationKindString(DeclarationKind kind) case DeclarationKind::Import: return "import"; case DeclarationKind::BodyLevelFunction: + case DeclarationKind::ModuleBodyLevelFunction: case DeclarationKind::LexicalFunction: return "function"; case DeclarationKind::VarForAnnexBLexicalFunction: @@ -127,7 +144,8 @@ StatementKindIsBraced(StatementKind kind) kind == StatementKind::Switch || kind == StatementKind::Try || kind == StatementKind::Catch || - kind == StatementKind::Finally; + kind == StatementKind::Finally || + kind == StatementKind::Class; } void @@ -189,11 +207,12 @@ ParseContext::Scope::addCatchParameters(ParseContext* pc, Scope& catchParamScope for (DeclaredNameMap::Range r = catchParamScope.declared_->all(); !r.empty(); r.popFront()) { DeclarationKind kind = r.front().value()->kind(); + uint32_t pos = r.front().value()->pos(); MOZ_ASSERT(DeclarationKindIsCatchParameter(kind)); JSAtom* name = r.front().key(); AddDeclaredNamePtr p = lookupDeclaredNameForAdd(name); MOZ_ASSERT(!p); - if (!addDeclaredName(pc, p, name, kind)) + if (!addDeclaredName(pc, p, name, kind, pos)) return false; } @@ -332,7 +351,8 @@ ParseContext::init() namedLambdaScope_->lookupDeclaredNameForAdd(fun->explicitName()); MOZ_ASSERT(!p); if (!namedLambdaScope_->addDeclaredName(this, p, fun->explicitName(), - DeclarationKind::Const)) + DeclarationKind::Const, + DeclaredNameInfo::npos)) { return false; } @@ -441,7 +461,7 @@ UsedNameTracker::rewind(RewindToken token) } FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead, - JSFunction* fun, uint32_t preludeStart, + JSFunction* fun, uint32_t toStringStart, Directives directives, bool extraWarnings, GeneratorKind generatorKind, FunctionAsyncKind asyncKind) : ObjectBox(fun, traceListHead), @@ -455,7 +475,8 @@ FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* trac bufEnd(0), startLine(1), startColumn(0), - preludeStart(preludeStart), + toStringStart(toStringStart), + toStringEnd(0), length(0), generatorKindBits_(GeneratorKindAsBits(generatorKind)), asyncKindBits_(AsyncKindAsBits(asyncKind)), @@ -474,6 +495,7 @@ FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* trac usesThis(false), usesReturn(false), hasRest_(false), + isExprBody_(false), funCxFlags() { // Functions created at parse time may be set singleton after parsing and @@ -525,10 +547,16 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt allowNewTarget_ = true; allowSuperProperty_ = fun->allowSuperProperty(); - if (kind == DerivedClassConstructor) { - setDerivedClassConstructor(); - allowSuperCall_ = true; - needsThisTDZChecks_ = true; + if (kind == ClassConstructor || kind == DerivedClassConstructor) { + auto stmt = enclosing->findInnermostStatement<ParseContext::ClassStatement>(); + MOZ_ASSERT(stmt); + stmt->constructorBox = this; + + if (kind == DerivedClassConstructor) { + setDerivedClassConstructor(); + allowSuperCall_ = true; + needsThisTDZChecks_ = true; + } } if (isGenexpLambda) @@ -570,97 +598,136 @@ FunctionBox::initWithEnclosingScope(Scope* enclosingScope) computeInWith(enclosingScope); } -template <typename ParseHandler> void -Parser<ParseHandler>::error(unsigned errorNumber, ...) +ParserBase::error(unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); #ifdef DEBUG bool result = #endif - tokenStream.reportCompileErrorNumberVA(pos().begin, JSREPORT_ERROR, errorNumber, args); + tokenStream.reportCompileErrorNumberVA(nullptr, pos().begin, JSREPORT_ERROR, + errorNumber, args); MOZ_ASSERT(!result, "reporting an error returned true?"); va_end(args); } -template <typename ParseHandler> void -Parser<ParseHandler>::errorAt(uint32_t offset, unsigned errorNumber, ...) +ParserBase::errorWithNotes(UniquePtr<JSErrorNotes> notes, unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); #ifdef DEBUG bool result = #endif - tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_ERROR, errorNumber, args); + tokenStream.reportCompileErrorNumberVA(Move(notes), pos().begin, JSREPORT_ERROR, + errorNumber, args); + MOZ_ASSERT(!result, "reporting an error returned true?"); + va_end(args); +} + +void +ParserBase::errorAt(uint32_t offset, unsigned errorNumber, ...) +{ + va_list args; + va_start(args, errorNumber); +#ifdef DEBUG + bool result = +#endif + tokenStream.reportCompileErrorNumberVA(nullptr, offset, JSREPORT_ERROR, errorNumber, args); + MOZ_ASSERT(!result, "reporting an error returned true?"); + va_end(args); +} + +void +ParserBase::errorWithNotesAt(UniquePtr<JSErrorNotes> notes, uint32_t offset, + unsigned errorNumber, ...) +{ + va_list args; + va_start(args, errorNumber); +#ifdef DEBUG + bool result = +#endif + tokenStream.reportCompileErrorNumberVA(Move(notes), offset, JSREPORT_ERROR, + errorNumber, args); MOZ_ASSERT(!result, "reporting an error returned true?"); va_end(args); } -template <typename ParseHandler> bool -Parser<ParseHandler>::warning(unsigned errorNumber, ...) +ParserBase::warning(unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); bool result = - tokenStream.reportCompileErrorNumberVA(pos().begin, JSREPORT_WARNING, errorNumber, args); + tokenStream.reportCompileErrorNumberVA(nullptr, pos().begin, JSREPORT_WARNING, + errorNumber, args); va_end(args); return result; } -template <typename ParseHandler> bool -Parser<ParseHandler>::warningAt(uint32_t offset, unsigned errorNumber, ...) +ParserBase::warningAt(uint32_t offset, unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); bool result = - tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_WARNING, errorNumber, args); + tokenStream.reportCompileErrorNumberVA(nullptr, offset, JSREPORT_WARNING, + errorNumber, args); va_end(args); return result; } -template <typename ParseHandler> bool -Parser<ParseHandler>::extraWarning(unsigned errorNumber, ...) +ParserBase::extraWarning(unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - bool result = tokenStream.reportExtraWarningErrorNumberVA(pos().begin, errorNumber, args); + bool result = tokenStream.reportExtraWarningErrorNumberVA(nullptr, pos().begin, + errorNumber, args); va_end(args); return result; } -template <typename ParseHandler> bool -Parser<ParseHandler>::strictModeError(unsigned errorNumber, ...) +ParserBase::extraWarningAt(uint32_t offset, unsigned errorNumber, ...) +{ + va_list args; + va_start(args, errorNumber); + + bool result = + tokenStream.reportExtraWarningErrorNumberVA(nullptr, offset, errorNumber, args); + + va_end(args); + return result; +} + +bool +ParserBase::strictModeError(unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); bool res = - tokenStream.reportStrictModeErrorNumberVA(pos().begin, pc->sc()->strict(), + tokenStream.reportStrictModeErrorNumberVA(nullptr, pos().begin, pc->sc()->strict(), errorNumber, args); va_end(args); return res; } -template <typename ParseHandler> bool -Parser<ParseHandler>::strictModeErrorAt(uint32_t offset, unsigned errorNumber, ...) +ParserBase::strictModeErrorAt(uint32_t offset, unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); bool res = - tokenStream.reportStrictModeErrorNumberVA(offset, pc->sc()->strict(), errorNumber, args); + tokenStream.reportStrictModeErrorNumberVA(nullptr, offset, pc->sc()->strict(), + errorNumber, args); va_end(args); return res; } -template <typename ParseHandler> bool -Parser<ParseHandler>::reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumber, ...) +ParserBase::reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); @@ -668,17 +735,21 @@ Parser<ParseHandler>::reportNoOffset(ParseReportKind kind, bool strict, unsigned uint32_t offset = TokenStream::NoOffset; switch (kind) { case ParseError: - result = tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_ERROR, errorNumber, args); + result = tokenStream.reportCompileErrorNumberVA(nullptr, offset, JSREPORT_ERROR, + errorNumber, args); break; case ParseWarning: result = - tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_WARNING, errorNumber, args); + tokenStream.reportCompileErrorNumberVA(nullptr, offset, JSREPORT_WARNING, + errorNumber, args); break; case ParseExtraWarning: - result = tokenStream.reportExtraWarningErrorNumberVA(offset, errorNumber, args); + result = tokenStream.reportExtraWarningErrorNumberVA(nullptr, offset, + errorNumber, args); break; case ParseStrictError: - result = tokenStream.reportStrictModeErrorNumberVA(offset, strict, errorNumber, args); + result = tokenStream.reportStrictModeErrorNumberVA(nullptr, offset, strict, + errorNumber, args); break; } va_end(args); @@ -686,7 +757,7 @@ Parser<ParseHandler>::reportNoOffset(ParseReportKind kind, bool strict, unsigned } template <> -bool +inline bool Parser<FullParseHandler>::abortIfSyntaxParser() { handler.disableSyntaxParser(); @@ -694,23 +765,21 @@ Parser<FullParseHandler>::abortIfSyntaxParser() } template <> -bool +inline bool Parser<SyntaxParseHandler>::abortIfSyntaxParser() { abortedSyntaxParse = true; return false; } -template <typename ParseHandler> -Parser<ParseHandler>::Parser(ExclusiveContext* cx, LifoAlloc& alloc, - const ReadOnlyCompileOptions& options, - const char16_t* chars, size_t length, - bool foldConstants, - UsedNameTracker& usedNames, - Parser<SyntaxParseHandler>* syntaxParser, - LazyScript* lazyOuterFunction) - : AutoGCRooter(cx, PARSER), - context(cx), +ParserBase::ParserBase(ExclusiveContext* cx, LifoAlloc& alloc, + const ReadOnlyCompileOptions& options, + const char16_t* chars, size_t length, + bool foldConstants, + UsedNameTracker& usedNames, + Parser<SyntaxParseHandler>* syntaxParser, + LazyScript* lazyOuterFunction) + : context(cx), alloc(alloc), tokenStream(cx, options, chars, length, thisForCtor()), traceListHead(nullptr), @@ -725,17 +794,44 @@ Parser<ParseHandler>::Parser(ExclusiveContext* cx, LifoAlloc& alloc, #endif abortedSyntaxParse(false), isUnexpectedEOF_(false), - handler(cx, alloc, tokenStream, syntaxParser, lazyOuterFunction) + awaitIsKeyword_(false) { cx->perThreadData->frontendCollectionPool.addActiveCompilation(); + tempPoolMark = alloc.mark(); +} + +ParserBase::~ParserBase() +{ + alloc.release(tempPoolMark); + + /* + * The parser can allocate enormous amounts of memory for large functions. + * Eagerly free the memory now (which otherwise won't be freed until the + * next GC) to avoid unnecessary OOMs. + */ + alloc.freeAllIfHugeAndUnused(); + + context->perThreadData->frontendCollectionPool.removeActiveCompilation(); +} +template <typename ParseHandler> +Parser<ParseHandler>::Parser(ExclusiveContext* cx, LifoAlloc& alloc, + const ReadOnlyCompileOptions& options, + const char16_t* chars, size_t length, + bool foldConstants, + UsedNameTracker& usedNames, + Parser<SyntaxParseHandler>* syntaxParser, + LazyScript* lazyOuterFunction) + : ParserBase(cx, alloc, options, chars, length, foldConstants, usedNames, syntaxParser, + lazyOuterFunction), + AutoGCRooter(cx, PARSER), + handler(cx, alloc, tokenStream, syntaxParser, lazyOuterFunction) +{ // The Mozilla specific JSOPTION_EXTRA_WARNINGS option adds extra warnings // which are not generated if functions are parsed lazily. Note that the // standard "use strict" does not inhibit lazy parsing. if (options.extraWarningsOption) handler.disableSyntaxParser(); - - tempPoolMark = alloc.mark(); } template<typename ParseHandler> @@ -746,26 +842,29 @@ Parser<ParseHandler>::checkOptions() checkOptionsCalled = true; #endif - if (!tokenStream.checkOptions()) - return false; - - return true; + return tokenStream.checkOptions(); } template <typename ParseHandler> Parser<ParseHandler>::~Parser() { MOZ_ASSERT(checkOptionsCalled); - alloc.release(tempPoolMark); +} - /* - * The parser can allocate enormous amounts of memory for large functions. - * Eagerly free the memory now (which otherwise won't be freed until the - * next GC) to avoid unnecessary OOMs. - */ - alloc.freeAllIfHugeAndUnused(); +template <> +void +Parser<SyntaxParseHandler>::setAwaitIsKeyword(bool isKeyword) +{ + awaitIsKeyword_ = isKeyword; +} - context->perThreadData->frontendCollectionPool.removeActiveCompilation(); +template <> +void +Parser<FullParseHandler>::setAwaitIsKeyword(bool isKeyword) +{ + awaitIsKeyword_ = isKeyword; + if (Parser<SyntaxParseHandler>* parser = handler.syntaxParser) + parser->setAwaitIsKeyword(isKeyword); } template <typename ParseHandler> @@ -795,7 +894,7 @@ Parser<ParseHandler>::newObjectBox(JSObject* obj) template <typename ParseHandler> FunctionBox* -Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun, uint32_t preludeStart, +Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun, uint32_t toStringStart, Directives inheritedDirectives, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB) @@ -811,7 +910,7 @@ Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun, uint32_t preludeS * function. */ FunctionBox* funbox = - alloc.new_<FunctionBox>(context, alloc, traceListHead, fun, preludeStart, + alloc.new_<FunctionBox>(context, alloc, traceListHead, fun, toStringStart, inheritedDirectives, options().extraWarningsOption, generatorKind, asyncKind); if (!funbox) { @@ -894,38 +993,17 @@ Parser<ParseHandler>::parse() /* * Strict mode forbids introducing new definitions for 'eval', 'arguments', or - * for any strict mode reserved keyword. + * for any strict mode reserved word. */ -template <typename ParseHandler> bool -Parser<ParseHandler>::isValidStrictBinding(PropertyName* name) +ParserBase::isValidStrictBinding(PropertyName* name) { return name != context->names().eval && name != context->names().arguments && name != context->names().let && name != context->names().static_ && - !(IsKeyword(name) && name != context->names().await); -} - -/* - * Check that it is permitted to introduce a binding for |name|. Use |pos| for - * reporting error locations. - */ -template <typename ParseHandler> -bool -Parser<ParseHandler>::checkStrictBinding(PropertyName* name, TokenPos pos) -{ - if (!pc->sc()->needStrictChecks()) - return true; - - if (!isValidStrictBinding(name)) { - JSAutoByteString bytes; - if (!AtomToPrintableString(context, name, &bytes)) - return false; - return strictModeErrorAt(pos.begin, JSMSG_BAD_BINDING, bytes.ptr()); - } - - return true; + name != context->names().yield && + !IsStrictReservedWord(name); } /* @@ -952,13 +1030,71 @@ Parser<ParseHandler>::hasValidSimpleStrictParameterNames() template <typename ParseHandler> void -Parser<ParseHandler>::reportRedeclaration(HandlePropertyName name, DeclarationKind kind, - TokenPos pos) +Parser<ParseHandler>::reportMissingClosing(unsigned errorNumber, unsigned noteNumber, + uint32_t openedPos) +{ + auto notes = MakeUnique<JSErrorNotes>(); + if (!notes) + return; + + uint32_t line, column; + tokenStream.srcCoords.lineNumAndColumnIndex(openedPos, &line, &column); + + const size_t MaxWidth = sizeof("4294967295"); + char columnNumber[MaxWidth]; + SprintfLiteral(columnNumber, "%" PRIu32, column); + char lineNumber[MaxWidth]; + SprintfLiteral(lineNumber, "%" PRIu32, line); + + if (!notes->addNoteASCII(pc->sc()->context, + getFilename(), line, column, + GetErrorMessage, nullptr, + noteNumber, lineNumber, columnNumber)) + { + return; + } + + errorWithNotes(Move(notes), errorNumber); +} + +template <typename ParseHandler> +void +Parser<ParseHandler>::reportRedeclaration(HandlePropertyName name, DeclarationKind prevKind, + TokenPos pos, uint32_t prevPos) { JSAutoByteString bytes; if (!AtomToPrintableString(context, name, &bytes)) return; - errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(kind), bytes.ptr()); + + if (prevPos == DeclaredNameInfo::npos) { + errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(prevKind), bytes.ptr()); + return; + } + + auto notes = MakeUnique<JSErrorNotes>(); + if (!notes) + return; + + uint32_t line, column; + tokenStream.srcCoords.lineNumAndColumnIndex(prevPos, &line, &column); + + const size_t MaxWidth = sizeof("4294967295"); + char columnNumber[MaxWidth]; + SprintfLiteral(columnNumber, "%" PRIu32, column); + char lineNumber[MaxWidth]; + SprintfLiteral(lineNumber, "%" PRIu32, line); + + if (!notes->addNoteASCII(pc->sc()->context, + getFilename(), line, column, + GetErrorMessage, nullptr, + JSMSG_REDECLARED_PREV, + lineNumber, columnNumber)) + { + return; + } + + errorWithNotesAt(Move(notes), pos.begin, JSMSG_REDECLARED_VAR, + DeclarationKindString(prevKind), bytes.ptr()); } // notePositionalFormalParameter is called for both the arguments of a regular @@ -973,6 +1109,7 @@ Parser<ParseHandler>::reportRedeclaration(HandlePropertyName name, DeclarationKi template <typename ParseHandler> bool Parser<ParseHandler>::notePositionalFormalParameter(Node fn, HandlePropertyName name, + uint32_t beginPos, bool disallowDuplicateParams, bool* duplicatedParam) { @@ -997,7 +1134,7 @@ Parser<ParseHandler>::notePositionalFormalParameter(Node fn, HandlePropertyName *duplicatedParam = true; } else { DeclarationKind kind = DeclarationKind::PositionalFormalParameter; - if (!pc->functionScope().addDeclaredName(pc, p, name, kind)) + if (!pc->functionScope().addDeclaredName(pc, p, name, kind, beginPos)) return false; } @@ -1010,9 +1147,6 @@ Parser<ParseHandler>::notePositionalFormalParameter(Node fn, HandlePropertyName if (!paramNode) return false; - if (!checkStrictBinding(name, pos())) - return false; - handler.addFunctionFormalParameter(fn, paramNode); return true; } @@ -1104,8 +1238,8 @@ DeclarationKindIsParameter(DeclarationKind kind) template <typename ParseHandler> bool -Parser<ParseHandler>::tryDeclareVar(HandlePropertyName name, DeclarationKind kind, - Maybe<DeclarationKind>* redeclaredKind) +Parser<ParseHandler>::tryDeclareVar(HandlePropertyName name, DeclarationKind kind, uint32_t beginPos, + Maybe<DeclarationKind>* redeclaredKind, uint32_t* prevPos) { MOZ_ASSERT(DeclarationKindIsVar(kind)); @@ -1178,17 +1312,29 @@ Parser<ParseHandler>::tryDeclareVar(HandlePropertyName name, DeclarationKind kin if (!annexB35Allowance && !annexB33Allowance) { *redeclaredKind = Some(declaredKind); + *prevPos = p->value()->pos(); return true; } + } else if (kind == DeclarationKind::VarForAnnexBLexicalFunction) { + MOZ_ASSERT(DeclarationKindIsParameter(declaredKind)); + + // Annex B.3.3.1 disallows redeclaring parameter names. + // We don't need to set *prevPos here since this case is not + // an error. + *redeclaredKind = Some(declaredKind); + return true; } } else { - if (!scope->addDeclaredName(pc, p, name, kind)) + if (!scope->addDeclaredName(pc, p, name, kind, beginPos)) return false; } } - if (!pc->sc()->strict() && pc->sc()->isEvalContext()) + if (!pc->sc()->strict() && pc->sc()->isEvalContext()) { *redeclaredKind = isVarRedeclaredInEval(name, kind); + // We don't have position information at runtime. + *prevPos = DeclaredNameInfo::npos; + } return true; } @@ -1196,11 +1342,34 @@ Parser<ParseHandler>::tryDeclareVar(HandlePropertyName name, DeclarationKind kin template <typename ParseHandler> bool Parser<ParseHandler>::tryDeclareVarForAnnexBLexicalFunction(HandlePropertyName name, - bool* tryAnnexB) + uint32_t beginPos, bool* tryAnnexB) { Maybe<DeclarationKind> redeclaredKind; - if (!tryDeclareVar(name, DeclarationKind::VarForAnnexBLexicalFunction, &redeclaredKind)) + uint32_t unused; + if (!tryDeclareVar(name, DeclarationKind::VarForAnnexBLexicalFunction, beginPos, + &redeclaredKind, &unused)) + { return false; + } + + if (!redeclaredKind && pc->isFunctionBox()) { + ParseContext::Scope& funScope = pc->functionScope(); + ParseContext::Scope& varScope = pc->varScope(); + if (&funScope != &varScope) { + // Annex B.3.3.1 disallows redeclaring parameter names. In the + // presence of parameter expressions, parameter names are on the + // function scope, which encloses the var scope. This means + // tryDeclareVar call above would not catch this case, so test it + // manually. + if (AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(name)) { + DeclarationKind declaredKind = p->value()->kind(); + if (DeclarationKindIsParameter(declaredKind)) + redeclaredKind = Some(declaredKind); + else + MOZ_ASSERT(FunctionScope::isSpecialName(context, name)); + } + } + } if (redeclaredKind) { // If an early error would have occurred, undo all the @@ -1248,25 +1417,41 @@ Parser<ParseHandler>::noteDeclaredName(HandlePropertyName name, DeclarationKind if (pc->useAsmOrInsideUseAsm()) return true; - if (!checkStrictBinding(name, pos)) - return false; - switch (kind) { case DeclarationKind::Var: case DeclarationKind::BodyLevelFunction: case DeclarationKind::ForOfVar: { Maybe<DeclarationKind> redeclaredKind; - if (!tryDeclareVar(name, kind, &redeclaredKind)) + uint32_t prevPos; + if (!tryDeclareVar(name, kind, pos.begin, &redeclaredKind, &prevPos)) return false; if (redeclaredKind) { - reportRedeclaration(name, *redeclaredKind, pos); + reportRedeclaration(name, *redeclaredKind, pos, prevPos); return false; } break; } + case DeclarationKind::ModuleBodyLevelFunction: { + MOZ_ASSERT(pc->atModuleLevel()); + + AddDeclaredNamePtr p = pc->varScope().lookupDeclaredNameForAdd(name); + if (p) { + reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos()); + return false; + } + + if (!pc->varScope().addDeclaredName(pc, p, name, kind, pos.begin)) + return false; + + // Body-level functions in modules are always closed over. + pc->varScope().lookupDeclaredName(name)->value()->setClosedOver(); + + break; + } + case DeclarationKind::FormalParameter: { // It is an early error if any non-positional formal parameter name // (e.g., destructuring formal parameter) is duplicated. @@ -1277,7 +1462,7 @@ Parser<ParseHandler>::noteDeclaredName(HandlePropertyName name, DeclarationKind return false; } - if (!pc->functionScope().addDeclaredName(pc, p, name, kind)) + if (!pc->functionScope().addDeclaredName(pc, p, name, kind, pos.begin)) return false; break; @@ -1300,7 +1485,7 @@ Parser<ParseHandler>::noteDeclaredName(HandlePropertyName name, DeclarationKind (p->value()->kind() != DeclarationKind::LexicalFunction && p->value()->kind() != DeclarationKind::VarForAnnexBLexicalFunction)) { - reportRedeclaration(name, p->value()->kind(), pos); + reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos()); return false; } @@ -1308,7 +1493,7 @@ Parser<ParseHandler>::noteDeclaredName(HandlePropertyName name, DeclarationKind // declaration that shadows the VarForAnnexBLexicalFunction. p->value()->alterKind(kind); } else { - if (!scope->addDeclaredName(pc, p, name, kind)) + if (!scope->addDeclaredName(pc, p, name, kind, pos.begin)) return false; } @@ -1348,7 +1533,7 @@ Parser<ParseHandler>::noteDeclaredName(HandlePropertyName name, DeclarationKind if (pc->isFunctionExtraBodyVarScopeInnermost()) { DeclaredNamePtr p = pc->functionScope().lookupDeclaredName(name); if (p && DeclarationKindIsParameter(p->value()->kind())) { - reportRedeclaration(name, p->value()->kind(), pos); + reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos()); return false; } } @@ -1365,12 +1550,12 @@ Parser<ParseHandler>::noteDeclaredName(HandlePropertyName name, DeclarationKind p = scope->lookupDeclaredNameForAdd(name); MOZ_ASSERT(!p); } else { - reportRedeclaration(name, p->value()->kind(), pos); + reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos()); return false; } } - if (!p && !scope->addDeclaredName(pc, p, name, kind)) + if (!p && !scope->addDeclaredName(pc, p, name, kind, pos.begin)) return false; break; @@ -1742,11 +1927,8 @@ Parser<FullParseHandler>::newFunctionScopeData(ParseContext::Scope& scope, bool case BindingKind::Var: // The only vars in the function scope when there are parameter // exprs, which induces a separate var environment, should be the - // special internal bindings. - MOZ_ASSERT_IF(hasParameterExprs, - bi.name() == context->names().arguments || - bi.name() == context->names().dotThis || - bi.name() == context->names().dotGenerator); + // special bindings. + MOZ_ASSERT_IF(hasParameterExprs, FunctionScope::isSpecialName(context, bi.name())); if (!vars.append(binding)) return Nothing(); break; @@ -2031,7 +2213,7 @@ Parser<FullParseHandler>::moduleBody(ModuleSharedContext* modulesc) if (!mn) return null(); - AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, true); + AutoAwaitIsKeyword<FullParseHandler> awaitIsKeyword(this, true); ParseNode* pn = statementList(YieldIsKeyword); if (!pn) return null(); @@ -2123,8 +2305,11 @@ Parser<ParseHandler>::declareFunctionThis() ParseContext::Scope& funScope = pc->functionScope(); AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotThis); MOZ_ASSERT(!p); - if (!funScope.addDeclaredName(pc, p, dotThis, DeclarationKind::Var)) + if (!funScope.addDeclaredName(pc, p, dotThis, DeclarationKind::Var, + DeclaredNameInfo::npos)) + { return false; + } funbox->setHasThisBinding(); } @@ -2166,14 +2351,17 @@ Parser<ParseHandler>::declareDotGeneratorName() ParseContext::Scope& funScope = pc->functionScope(); HandlePropertyName dotGenerator = context->names().dotGenerator; AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotGenerator); - if (!p && !funScope.addDeclaredName(pc, p, dotGenerator, DeclarationKind::Var)) + if (!p && !funScope.addDeclaredName(pc, p, dotGenerator, DeclarationKind::Var, + DeclaredNameInfo::npos)) + { return false; + } return true; } template <typename ParseHandler> bool -Parser<ParseHandler>::finishFunctionScopes() +Parser<ParseHandler>::finishFunctionScopes(bool isStandaloneFunction) { FunctionBox* funbox = pc->functionBox(); @@ -2182,7 +2370,7 @@ Parser<ParseHandler>::finishFunctionScopes() return false; } - if (funbox->function()->isNamedLambda()) { + if (funbox->function()->isNamedLambda() && !isStandaloneFunction) { if (!propagateFreeNamesAndMarkClosedOverBindings(pc->namedLambdaScope())) return false; } @@ -2192,9 +2380,9 @@ Parser<ParseHandler>::finishFunctionScopes() template <> bool -Parser<FullParseHandler>::finishFunction() +Parser<FullParseHandler>::finishFunction(bool isStandaloneFunction /* = false */) { - if (!finishFunctionScopes()) + if (!finishFunctionScopes(isStandaloneFunction)) return false; FunctionBox* funbox = pc->functionBox(); @@ -2215,7 +2403,7 @@ Parser<FullParseHandler>::finishFunction() funbox->functionScopeBindings().set(*bindings); } - if (funbox->function()->isNamedLambda()) { + if (funbox->function()->isNamedLambda() && !isStandaloneFunction) { Maybe<LexicalScope::Data*> bindings = newLexicalScopeData(pc->namedLambdaScope()); if (!bindings) return false; @@ -2227,14 +2415,14 @@ Parser<FullParseHandler>::finishFunction() template <> bool -Parser<SyntaxParseHandler>::finishFunction() +Parser<SyntaxParseHandler>::finishFunction(bool isStandaloneFunction /* = false */) { // The LazyScript for a lazily parsed function needs to know its set of // free variables and inner functions so that when it is fully parsed, we // can skip over any already syntax parsed inner functions and still // retain correct scope information. - if (!finishFunctionScopes()) + if (!finishFunctionScopes(isStandaloneFunction)) return false; // There are too many bindings or inner functions to be saved into the @@ -2251,7 +2439,7 @@ Parser<SyntaxParseHandler>::finishFunction() LazyScript* lazy = LazyScript::Create(context, fun, pc->closedOverBindingsForLazy(), pc->innerFunctionsForLazy, versionNumber(), funbox->bufStart, funbox->bufEnd, - funbox->preludeStart, + funbox->toStringStart, funbox->startLine, funbox->startColumn); if (!lazy) return false; @@ -2264,6 +2452,8 @@ Parser<SyntaxParseHandler>::finishFunction() lazy->setAsyncKind(funbox->asyncKind()); if (funbox->hasRest()) lazy->setHasRest(); + if (funbox->isExprBody()) + lazy->setIsExprBody(); if (funbox->isLikelyConstructorWrapper()) lazy->setLikelyConstructorWrapper(); if (funbox->isDerivedClassConstructor()) @@ -2340,7 +2530,7 @@ Parser<FullParseHandler>::standaloneFunction(HandleFunction fun, return null(); fn->pn_body = argsbody; - FunctionBox* funbox = newFunctionBox(fn, fun, /* preludeStart = */ 0, inheritedDirectives, + FunctionBox* funbox = newFunctionBox(fn, fun, /* toStringStart = */ 0, inheritedDirectives, generatorKind, asyncKind, /* tryAnnexB = */ false); if (!funbox) return null(); @@ -2352,9 +2542,9 @@ Parser<FullParseHandler>::standaloneFunction(HandleFunction fun, funpc.setIsStandaloneFunctionBody(); YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); - AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, asyncKind == AsyncFunction); + AutoAwaitIsKeyword<FullParseHandler> awaitIsKeyword(this, asyncKind == AsyncFunction); if (!functionFormalParametersAndBody(InAllowed, yieldHandling, fn, Statement, - parameterListEnd)) + parameterListEnd, /* isStandaloneFunction = */ true)) { return null(); } @@ -2414,8 +2604,11 @@ Parser<ParseHandler>::declareFunctionArgumentsObject() if (tryDeclareArguments) { AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(argumentsName); if (!p) { - if (!funScope.addDeclaredName(pc, p, argumentsName, DeclarationKind::Var)) + if (!funScope.addDeclaredName(pc, p, argumentsName, DeclarationKind::Var, + DeclaredNameInfo::npos)) + { return false; + } funbox->declaredArguments = true; funbox->usesArguments = true; } else if (hasExtraBodyVarScope) { @@ -2631,39 +2824,59 @@ Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind, /* * WARNING: Do not call this function directly. - * Call either MatchOrInsertSemicolonAfterExpression or - * MatchOrInsertSemicolonAfterNonExpression instead, depending on context. + * Call either matchOrInsertSemicolonAfterExpression or + * matchOrInsertSemicolonAfterNonExpression instead, depending on context. */ -static bool -MatchOrInsertSemicolonHelper(TokenStream& ts, TokenStream::Modifier modifier) +template <typename ParseHandler> +bool +Parser<ParseHandler>::matchOrInsertSemicolonHelper(TokenStream::Modifier modifier) { TokenKind tt = TOK_EOF; - if (!ts.peekTokenSameLine(&tt, modifier)) + if (!tokenStream.peekTokenSameLine(&tt, modifier)) return false; if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) { + /* + * When current token is `await` and it's outside of async function, + * it's possibly intended to be an await expression. + * + * await f(); + * ^ + * | + * tried to insert semicolon here + * + * Detect this situation and throw an understandable error. Otherwise + * we'd throw a confusing "missing ; before statement" error. + */ + if (!pc->isAsync() && tokenStream.currentToken().type == TOK_AWAIT) { + error(JSMSG_AWAIT_OUTSIDE_ASYNC); + return false; + } + /* Advance the scanner for proper error location reporting. */ - ts.consumeKnownToken(tt, modifier); - ts.reportError(JSMSG_SEMI_BEFORE_STMNT); + tokenStream.consumeKnownToken(tt, modifier); + error(JSMSG_SEMI_BEFORE_STMNT); return false; } bool matched; - if (!ts.matchToken(&matched, TOK_SEMI, modifier)) + if (!tokenStream.matchToken(&matched, TOK_SEMI, modifier)) return false; if (!matched && modifier == TokenStream::None) - ts.addModifierException(TokenStream::OperandIsNone); + tokenStream.addModifierException(TokenStream::OperandIsNone); return true; } -static bool -MatchOrInsertSemicolonAfterExpression(TokenStream& ts) +template <typename ParseHandler> +bool +Parser<ParseHandler>::matchOrInsertSemicolonAfterExpression() { - return MatchOrInsertSemicolonHelper(ts, TokenStream::None); + return matchOrInsertSemicolonHelper(TokenStream::None); } -static bool -MatchOrInsertSemicolonAfterNonExpression(TokenStream& ts) +template <typename ParseHandler> +bool +Parser<ParseHandler>::matchOrInsertSemicolonAfterNonExpression() { - return MatchOrInsertSemicolonHelper(ts, TokenStream::Operand); + return matchOrInsertSemicolonHelper(TokenStream::Operand); } template <typename ParseHandler> @@ -2757,7 +2970,7 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn firstTokenModifier = funbox->isAsync() ? TokenStream::None : TokenStream::Operand; if (!tokenStream.peekToken(&tt, firstTokenModifier)) return false; - if (tt == TOK_NAME || tt == TOK_YIELD) { + if (TokenKindIsPossibleIdentifier(tt)) { parenFreeArrow = true; argModifier = firstTokenModifier; } @@ -2813,7 +3026,7 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn if (!tokenStream.getToken(&tt, argModifier)) return false; argModifier = TokenStream::Operand; - MOZ_ASSERT_IF(parenFreeArrow, tt == TOK_NAME || tt == TOK_YIELD); + MOZ_ASSERT_IF(parenFreeArrow, TokenKindIsPossibleIdentifier(tt)); if (tt == TOK_TRIPLEDOT) { if (IsSetterKind(kind)) { @@ -2834,7 +3047,7 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn if (!tokenStream.getToken(&tt)) return false; - if (tt != TOK_NAME && tt != TOK_YIELD && tt != TOK_LB && tt != TOK_LC) { + if (!TokenKindIsPossibleIdentifier(tt) && tt != TOK_LB && tt != TOK_LC) { error(JSMSG_NO_REST_NAME); return false; } @@ -2864,26 +3077,21 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn break; } - case TOK_NAME: - case TOK_YIELD: { - if (parenFreeArrow) - funbox->setStart(tokenStream); - - if (funbox->isAsync() && tokenStream.currentName() == context->names().await) { - // `await` is already gotten as TOK_NAME for the following - // case: - // - // async await => 1 - error(JSMSG_RESERVED_ID, "await"); + default: { + if (!TokenKindIsPossibleIdentifier(tt)) { + error(JSMSG_MISSING_FORMAL); return false; } + if (parenFreeArrow) + funbox->setStart(tokenStream); + RootedPropertyName name(context, bindingIdentifier(yieldHandling)); if (!name) return false; - if (!notePositionalFormalParameter(funcpn, name, disallowDuplicateParams, - &duplicatedParam)) + if (!notePositionalFormalParameter(funcpn, name, pos().begin, + disallowDuplicateParams, &duplicatedParam)) { return false; } @@ -2892,10 +3100,6 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn break; } - - default: - error(JSMSG_MISSING_FORMAL); - return false; } if (positionalFormals.length() >= ARGNO_LIMIT) { @@ -2989,7 +3193,7 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn template <> bool -Parser<FullParseHandler>::skipLazyInnerFunction(ParseNode* pn, uint32_t preludeStart, +Parser<FullParseHandler>::skipLazyInnerFunction(ParseNode* pn, uint32_t toStringStart, FunctionSyntaxKind kind, bool tryAnnexB) { // When a lazily-parsed function is called, we only fully parse (and emit) @@ -2999,7 +3203,7 @@ Parser<FullParseHandler>::skipLazyInnerFunction(ParseNode* pn, uint32_t preludeS RootedFunction fun(context, handler.nextLazyInnerFunction()); MOZ_ASSERT(!fun->isLegacyGenerator()); - FunctionBox* funbox = newFunctionBox(pn, fun, preludeStart, Directives(/* strict = */ false), + FunctionBox* funbox = newFunctionBox(pn, fun, toStringStart, Directives(/* strict = */ false), fun->generatorKind(), fun->asyncKind(), tryAnnexB); if (!funbox) return false; @@ -3007,6 +3211,8 @@ Parser<FullParseHandler>::skipLazyInnerFunction(ParseNode* pn, uint32_t preludeS LazyScript* lazy = fun->lazyScript(); if (lazy->needsHomeObject()) funbox->setNeedsHomeObject(); + if (lazy->isExprBody()) + funbox->setIsExprBody(); PropagateTransitiveParseFlags(lazy, pc->sc()); @@ -3019,17 +3225,22 @@ Parser<FullParseHandler>::skipLazyInnerFunction(ParseNode* pn, uint32_t preludeS if (!tokenStream.advance(fun->lazyScript()->end() - userbufBase)) return false; - if (kind == Statement && fun->isExprBody()) { - if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) +#if JS_HAS_EXPR_CLOSURES + // Only expression closure can be Statement kind. + // If we remove expression closure, we can remove isExprBody flag from + // LazyScript and JSScript. + if (kind == Statement && funbox->isExprBody()) { + if (!matchOrInsertSemicolonAfterExpression()) return false; } +#endif return true; } template <> bool -Parser<SyntaxParseHandler>::skipLazyInnerFunction(Node pn, uint32_t preludeStart, +Parser<SyntaxParseHandler>::skipLazyInnerFunction(Node pn, uint32_t toStringStart, FunctionSyntaxKind kind, bool tryAnnexB) { MOZ_CRASH("Cannot skip lazy inner functions when syntax parsing"); @@ -3082,7 +3293,7 @@ template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::templateLiteral(YieldHandling yieldHandling) { - Node pn = noSubstitutionTemplate(); + Node pn = noSubstitutionUntaggedTemplate(); if (!pn) return null(); @@ -3095,7 +3306,7 @@ Parser<ParseHandler>::templateLiteral(YieldHandling yieldHandling) if (!addExprAndGetNextTemplStrToken(yieldHandling, nodeList, &tt)) return null(); - pn = noSubstitutionTemplate(); + pn = noSubstitutionUntaggedTemplate(); if (!pn) return null(); @@ -3106,7 +3317,7 @@ Parser<ParseHandler>::templateLiteral(YieldHandling yieldHandling) template <typename ParseHandler> typename ParseHandler::Node -Parser<ParseHandler>::functionDefinition(uint32_t preludeStart, Node pn, InHandling inHandling, +Parser<ParseHandler>::functionDefinition(uint32_t toStringStart, Node pn, InHandling inHandling, YieldHandling yieldHandling, HandleAtom funName, FunctionSyntaxKind kind, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, @@ -3119,7 +3330,7 @@ Parser<ParseHandler>::functionDefinition(uint32_t preludeStart, Node pn, InHandl // functions, which are also lazy. Instead, their free variables and // source extents are recorded and may be skipped. if (handler.canSkipLazyInnerFunctions()) { - if (!skipLazyInnerFunction(pn, preludeStart, kind, tryAnnexB)) + if (!skipLazyInnerFunction(pn, toStringStart, kind, tryAnnexB)) return null(); return pn; } @@ -3152,7 +3363,7 @@ Parser<ParseHandler>::functionDefinition(uint32_t preludeStart, Node pn, InHandl // reparse a function due to failed syntax parsing and encountering new // "use foo" directives. while (true) { - if (trySyntaxParseInnerFunction(pn, fun, preludeStart, inHandling, yieldHandling, kind, + if (trySyntaxParseInnerFunction(pn, fun, toStringStart, inHandling, yieldHandling, kind, generatorKind, asyncKind, tryAnnexB, directives, &newDirectives)) { @@ -3181,7 +3392,7 @@ Parser<ParseHandler>::functionDefinition(uint32_t preludeStart, Node pn, InHandl template <> bool Parser<FullParseHandler>::trySyntaxParseInnerFunction(ParseNode* pn, HandleFunction fun, - uint32_t preludeStart, + uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind, @@ -3215,13 +3426,13 @@ Parser<FullParseHandler>::trySyntaxParseInnerFunction(ParseNode* pn, HandleFunct // Make a FunctionBox before we enter the syntax parser, because |pn| // still expects a FunctionBox to be attached to it during BCE, and // the syntax parser cannot attach one to it. - FunctionBox* funbox = newFunctionBox(pn, fun, preludeStart, inheritedDirectives, + FunctionBox* funbox = newFunctionBox(pn, fun, toStringStart, inheritedDirectives, generatorKind, asyncKind, tryAnnexB); if (!funbox) return false; funbox->initWithEnclosingParseContext(pc, kind); - if (!parser->innerFunction(SyntaxParseHandler::NodeGeneric, pc, funbox, preludeStart, + if (!parser->innerFunction(SyntaxParseHandler::NodeGeneric, pc, funbox, toStringStart, inHandling, yieldHandling, kind, inheritedDirectives, newDirectives)) { @@ -3249,14 +3460,14 @@ Parser<FullParseHandler>::trySyntaxParseInnerFunction(ParseNode* pn, HandleFunct } while (false); // We failed to do a syntax parse above, so do the full parse. - return innerFunction(pn, pc, fun, preludeStart, inHandling, yieldHandling, kind, + return innerFunction(pn, pc, fun, toStringStart, inHandling, yieldHandling, kind, generatorKind, asyncKind, tryAnnexB, inheritedDirectives, newDirectives); } template <> bool Parser<SyntaxParseHandler>::trySyntaxParseInnerFunction(Node pn, HandleFunction fun, - uint32_t preludeStart, + uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind, @@ -3267,14 +3478,14 @@ Parser<SyntaxParseHandler>::trySyntaxParseInnerFunction(Node pn, HandleFunction Directives* newDirectives) { // This is already a syntax parser, so just parse the inner function. - return innerFunction(pn, pc, fun, preludeStart, inHandling, yieldHandling, kind, + return innerFunction(pn, pc, fun, toStringStart, inHandling, yieldHandling, kind, generatorKind, asyncKind, tryAnnexB, inheritedDirectives, newDirectives); } template <typename ParseHandler> bool Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, FunctionBox* funbox, - uint32_t preludeStart, + uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind, Directives inheritedDirectives, Directives* newDirectives) @@ -3298,7 +3509,7 @@ Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, FunctionBox* template <typename ParseHandler> bool Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, HandleFunction fun, - uint32_t preludeStart, + uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, @@ -3310,13 +3521,13 @@ Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, HandleFuncti // parser. In that case, outerpc is a ParseContext from the full parser // instead of the current top of the stack of the syntax parser. - FunctionBox* funbox = newFunctionBox(pn, fun, preludeStart, inheritedDirectives, + FunctionBox* funbox = newFunctionBox(pn, fun, toStringStart, inheritedDirectives, generatorKind, asyncKind, tryAnnexB); if (!funbox) return false; funbox->initWithEnclosingParseContext(outerpc, kind); - return innerFunction(pn, outerpc, funbox, preludeStart, inHandling, yieldHandling, kind, + return innerFunction(pn, outerpc, funbox, toStringStart, inHandling, yieldHandling, kind, inheritedDirectives, newDirectives); } @@ -3324,7 +3535,7 @@ template <typename ParseHandler> bool Parser<ParseHandler>::appendToCallSiteObj(Node callSiteObj) { - Node cookedNode = noSubstitutionTemplate(); + Node cookedNode = noSubstitutionTaggedTemplate(); if (!cookedNode) return false; @@ -3352,7 +3563,7 @@ Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, bool strict return null(); Directives directives(strict); - FunctionBox* funbox = newFunctionBox(pn, fun, /* preludeStart = */ 0, directives, + FunctionBox* funbox = newFunctionBox(pn, fun, /* toStringStart = */ 0, directives, generatorKind, asyncKind, /* tryAnnexB = */ false); if (!funbox) return null(); @@ -3402,7 +3613,8 @@ bool Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, YieldHandling yieldHandling, Node pn, FunctionSyntaxKind kind, - Maybe<uint32_t> parameterListEnd /* = Nothing() */) + Maybe<uint32_t> parameterListEnd /* = Nothing() */, + bool isStandaloneFunction /* = false */) { // Given a properly initialized parse context, try to parse an actual // function without concern for conversion to strict mode, use of lazy @@ -3411,9 +3623,14 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, FunctionBox* funbox = pc->functionBox(); RootedFunction fun(context, funbox->function()); - AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, funbox->isAsync()); - if (!functionArguments(yieldHandling, kind, pn)) - return false; + // See below for an explanation why arrow function parameters and arrow + // function bodies are parsed with different yield/await settings. + { + bool asyncOrArrowInAsync = funbox->isAsync() || (kind == Arrow && awaitIsKeyword()); + AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, asyncOrArrowInAsync); + if (!functionArguments(yieldHandling, kind, pn)) + return false; + } Maybe<ParseContext::VarScope> varScope; if (funbox->hasParameterExprs) { @@ -3446,6 +3663,7 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, TokenKind tt; if (!tokenStream.getToken(&tt, TokenStream::Operand)) return false; + uint32_t openedPos = 0; if (tt != TOK_LC) { if ((funbox->isStarGenerator() && !funbox->isAsync()) || kind == Method || kind == GetterNoExpressionClosure || kind == SetterNoExpressionClosure || @@ -3466,9 +3684,9 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, tokenStream.ungetToken(); bodyType = ExpressionBody; -#if JS_HAS_EXPR_CLOSURES - fun->setIsExprBody(); -#endif + funbox->setIsExprBody(); + } else { + openedPos = pos().begin; } // Arrow function parameters inherit yieldHandling from the enclosing @@ -3476,41 +3694,59 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, // |yield| in the parameters is either a name or keyword, depending on // whether the arrow function is enclosed in a generator function or not. // Whereas the |yield| in the function body is always parsed as a name. + // The same goes when parsing |await| in arrow functions. YieldHandling bodyYieldHandling = GetYieldHandling(pc->generatorKind(), pc->asyncKind()); - Node body = functionBody(inHandling, bodyYieldHandling, kind, bodyType); - if (!body) - return false; + Node body; + { + AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, funbox->isAsync()); + body = functionBody(inHandling, bodyYieldHandling, kind, bodyType); + if (!body) + return false; + } - if ((kind != Method && !IsConstructorKind(kind)) && fun->explicitName()) { + if ((kind == Statement || kind == Expression) && fun->explicitName()) { RootedPropertyName propertyName(context, fun->explicitName()->asPropertyName()); - if (!checkStrictBinding(propertyName, handler.getPosition(pn))) + YieldHandling nameYieldHandling; + if (kind == Expression) { + // Named lambda has binding inside it. + nameYieldHandling = bodyYieldHandling; + } else { + // Otherwise YieldHandling cannot be checked at this point + // because of different context. + // It should already be checked before this point. + nameYieldHandling = YieldIsName; + } + + // We already use the correct await-handling at this point, therefore + // we don't need call AutoAwaitIsKeyword here. + + if (!checkBindingIdentifier(propertyName, handler.getPosition(pn).begin, + nameYieldHandling)) + { return false; + } } if (bodyType == StatementListBody) { - bool matched; - if (!tokenStream.matchToken(&matched, TOK_RC, TokenStream::Operand)) - return false; - if (!matched) { - error(JSMSG_CURLY_AFTER_BODY); - return false; - } - funbox->bufEnd = pos().end; + MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand, + reportMissingClosing(JSMSG_CURLY_AFTER_BODY, + JSMSG_CURLY_OPENED, openedPos)); + funbox->setEnd(pos().end); } else { #if !JS_HAS_EXPR_CLOSURES MOZ_ASSERT(kind == Arrow); #endif if (tokenStream.hadError()) return false; - funbox->bufEnd = pos().end; - if (kind == Statement && !MatchOrInsertSemicolonAfterExpression(tokenStream)) + funbox->setEnd(pos().end); + if (kind == Statement && !matchOrInsertSemicolonAfterExpression()) return false; } if (IsMethodDefinitionKind(kind) && pc->superScopeNeedsHomeObject()) funbox->setNeedsHomeObject(); - if (!finishFunction()) + if (!finishFunction(isStandaloneFunction)) return false; handler.setEndPosition(body, pos().begin); @@ -3522,26 +3758,11 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, template <typename ParseHandler> typename ParseHandler::Node -Parser<ParseHandler>::functionStmt(uint32_t preludeStart, YieldHandling yieldHandling, +Parser<ParseHandler>::functionStmt(uint32_t toStringStart, YieldHandling yieldHandling, DefaultHandling defaultHandling, FunctionAsyncKind asyncKind) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); - // Annex B.3.4 says we can parse function declarations unbraced under if - // or else as if it were braced. That is, |if (x) function f() {}| is - // parsed as |if (x) { function f() {} }|. - Maybe<ParseContext::Statement> synthesizedStmtForAnnexB; - Maybe<ParseContext::Scope> synthesizedScopeForAnnexB; - if (!pc->sc()->strict()) { - ParseContext::Statement* stmt = pc->innermostStatement(); - if (stmt && stmt->kind() == StatementKind::If) { - synthesizedStmtForAnnexB.emplace(pc, StatementKind::Block); - synthesizedScopeForAnnexB.emplace(this); - if (!synthesizedScopeForAnnexB->init(pc)) - return null(); - } - } - // In sloppy mode, Annex B.3.2 allows labelled function declarations. // Otherwise it's a parse error. ParseContext::Statement* declaredInStmt = pc->innermostStatement(); @@ -3577,7 +3798,7 @@ Parser<ParseHandler>::functionStmt(uint32_t preludeStart, YieldHandling yieldHan } RootedPropertyName name(context); - if (tt == TOK_NAME || tt == TOK_YIELD) { + if (TokenKindIsPossibleIdentifier(tt)) { name = bindingIdentifier(yieldHandling); if (!name) return null(); @@ -3602,19 +3823,18 @@ Parser<ParseHandler>::functionStmt(uint32_t preludeStart, YieldHandling yieldHan // early error, do so. This 'var' binding would be assigned // the function object when its declaration is reached, not at // the start of the block. - if (!tryDeclareVarForAnnexBLexicalFunction(name, &tryAnnexB)) + if (!tryDeclareVarForAnnexBLexicalFunction(name, pos().begin, &tryAnnexB)) return null(); } if (!noteDeclaredName(name, DeclarationKind::LexicalFunction, pos())) return null(); } else { - if (!noteDeclaredName(name, DeclarationKind::BodyLevelFunction, pos())) + DeclarationKind kind = pc->atModuleLevel() + ? DeclarationKind::ModuleBodyLevelFunction + : DeclarationKind::BodyLevelFunction; + if (!noteDeclaredName(name, kind, pos())) return null(); - - // Body-level functions in modules are always closed over. - if (pc->atModuleLevel()) - pc->varScope().lookupDeclaredName(name)->value()->setClosedOver(); } Node pn = handler.newFunctionStatement(); @@ -3622,30 +3842,18 @@ Parser<ParseHandler>::functionStmt(uint32_t preludeStart, YieldHandling yieldHan return null(); YieldHandling newYieldHandling = GetYieldHandling(generatorKind, asyncKind); - Node fun = functionDefinition(preludeStart, pn, InAllowed, newYieldHandling, + return functionDefinition(toStringStart, pn, InAllowed, newYieldHandling, name, Statement, generatorKind, asyncKind, tryAnnexB); - if (!fun) - return null(); - - if (synthesizedStmtForAnnexB) { - Node synthesizedStmtList = handler.newStatementList(handler.getPosition(fun)); - if (!synthesizedStmtList) - return null(); - handler.addStatementToList(synthesizedStmtList, fun); - return finishLexicalScope(*synthesizedScopeForAnnexB, synthesizedStmtList); - } - - return fun; } template <typename ParseHandler> typename ParseHandler::Node -Parser<ParseHandler>::functionExpr(uint32_t preludeStart, InvokedPrediction invoked, +Parser<ParseHandler>::functionExpr(uint32_t toStringStart, InvokedPrediction invoked, FunctionAsyncKind asyncKind) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); - AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, asyncKind == AsyncFunction); + AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, asyncKind == AsyncFunction); GeneratorKind generatorKind = asyncKind == AsyncFunction ? StarGenerator : NotGenerator; TokenKind tt; if (!tokenStream.getToken(&tt)) @@ -3664,7 +3872,7 @@ Parser<ParseHandler>::functionExpr(uint32_t preludeStart, InvokedPrediction invo YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); RootedPropertyName name(context); - if (tt == TOK_NAME || tt == TOK_YIELD) { + if (TokenKindIsPossibleIdentifier(tt)) { name = bindingIdentifier(yieldHandling); if (!name) return null(); @@ -3679,7 +3887,7 @@ Parser<ParseHandler>::functionExpr(uint32_t preludeStart, InvokedPrediction invo if (invoked) pn = handler.setLikelyIIFE(pn); - return functionDefinition(preludeStart, pn, InAllowed, yieldHandling, name, Expression, + return functionDefinition(toStringStart, pn, InAllowed, yieldHandling, name, Expression, generatorKind, asyncKind); } @@ -3701,18 +3909,6 @@ IsEscapeFreeStringLiteral(const TokenPos& pos, JSAtom* str) return pos.begin + str->length() + 2 == pos.end; } -template <typename ParseHandler> -bool -Parser<ParseHandler>::checkUnescapedName() -{ - const Token& token = tokenStream.currentToken(); - if (!token.nameContainsEscape()) - return true; - - errorAt(token.pos.begin, JSMSG_ESCAPED_KEYWORD); - return false; -} - template <> bool Parser<SyntaxParseHandler>::asmJS(Node list) @@ -3931,7 +4127,7 @@ Parser<ParseHandler>::matchLabel(YieldHandling yieldHandling, MutableHandle<Prop if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand)) return false; - if (tt == TOK_NAME || tt == TOK_YIELD) { + if (TokenKindIsPossibleIdentifier(tt)) { tokenStream.consumeKnownToken(tt, TokenStream::Operand); label.set(labelIdentifier(yieldHandling)); @@ -3954,8 +4150,17 @@ Parser<ParseHandler>::PossibleError::error(ErrorKind kind) { if (kind == ErrorKind::Expression) return exprError_; - MOZ_ASSERT(kind == ErrorKind::Destructuring); - return destructuringError_; + if (kind == ErrorKind::Destructuring) + return destructuringError_; + MOZ_ASSERT(kind == ErrorKind::DestructuringWarning); + return destructuringWarning_; +} + +template <typename ParseHandler> +bool +Parser<ParseHandler>::PossibleError::hasPendingDestructuringError() +{ + return hasError(ErrorKind::Destructuring); } template <typename ParseHandler> @@ -3999,6 +4204,14 @@ Parser<ParseHandler>::PossibleError::setPendingDestructuringErrorAt(const TokenP template <typename ParseHandler> void +Parser<ParseHandler>::PossibleError::setPendingDestructuringWarningAt(const TokenPos& pos, + unsigned errorNumber) +{ + setPending(ErrorKind::DestructuringWarning, pos, errorNumber); +} + +template <typename ParseHandler> +void Parser<ParseHandler>::PossibleError::setPendingExpressionErrorAt(const TokenPos& pos, unsigned errorNumber) { @@ -4019,23 +4232,36 @@ Parser<ParseHandler>::PossibleError::checkForError(ErrorKind kind) template <typename ParseHandler> bool -Parser<ParseHandler>::PossibleError::checkForDestructuringError() +Parser<ParseHandler>::PossibleError::checkForWarning(ErrorKind kind) +{ + if (!hasError(kind)) + return true; + + Error& err = error(kind); + return parser_.extraWarningAt(err.offset_, err.errorNumber_); +} + +template <typename ParseHandler> +bool +Parser<ParseHandler>::PossibleError::checkForDestructuringErrorOrWarning() { // Clear pending expression error, because we're definitely not in an // expression context. setResolved(ErrorKind::Expression); - // Report any pending destructuring error. - return checkForError(ErrorKind::Destructuring); + // Report any pending destructuring error or warning. + return checkForError(ErrorKind::Destructuring) && + checkForWarning(ErrorKind::DestructuringWarning); } template <typename ParseHandler> bool Parser<ParseHandler>::PossibleError::checkForExpressionError() { - // Clear pending destructuring error, because we're definitely not in a - // destructuring context. + // Clear pending destructuring error or warning, because we're definitely + // not in a destructuring context. setResolved(ErrorKind::Destructuring); + setResolved(ErrorKind::DestructuringWarning); // Report any pending expression error. return checkForError(ErrorKind::Expression); @@ -4067,191 +4293,272 @@ Parser<ParseHandler>::PossibleError::transferErrorsTo(PossibleError* other) transferErrorTo(ErrorKind::Expression, other); } -template <> -bool -Parser<FullParseHandler>::checkDestructuringName(ParseNode* expr, Maybe<DeclarationKind> maybeDecl) +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::bindingInitializer(Node lhs, DeclarationKind kind, + YieldHandling yieldHandling) { - MOZ_ASSERT(!handler.isUnparenthesizedDestructuringPattern(expr)); - - // Parentheses are forbidden around destructuring *patterns* (but allowed - // around names). Use our nicer error message for parenthesized, nested - // patterns. - if (handler.isParenthesizedDestructuringPattern(expr)) { - errorAt(expr->pn_pos.begin, JSMSG_BAD_DESTRUCT_PARENS); - return false; - } + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_ASSIGN)); - // This expression might be in a variable-binding pattern where only plain, - // unparenthesized names are permitted. - if (maybeDecl) { - // Destructuring patterns in declarations must only contain - // unparenthesized names. - if (!handler.isUnparenthesizedName(expr)) { - errorAt(expr->pn_pos.begin, JSMSG_NO_VARIABLE_NAME); - return false; - } + if (kind == DeclarationKind::FormalParameter) + pc->functionBox()->hasParameterExprs = true; - RootedPropertyName name(context, expr->name()); - return noteDeclaredName(name, *maybeDecl, expr->pn_pos); - } + Node rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!rhs) + return null(); - // Otherwise this is an expression in destructuring outside a declaration. - if (handler.isNameAnyParentheses(expr)) { - if (const char* chars = handler.nameIsArgumentsEvalAnyParentheses(expr, context)) { - if (!strictModeErrorAt(expr->pn_pos.begin, JSMSG_BAD_STRICT_ASSIGN, chars)) - return false; - } + handler.checkAndSetIsDirectRHSAnonFunction(rhs); - return true; - } + Node assign = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP); + if (!assign) + return null(); - if (handler.isPropertyAccess(expr)) - return true; + if (foldConstants && !FoldConstants(context, &assign, this)) + return null(); - errorAt(expr->pn_pos.begin, JSMSG_BAD_DESTRUCT_TARGET); - return false; + return assign; } -template <> -bool -Parser<FullParseHandler>::checkDestructuringPattern(ParseNode* pattern, - Maybe<DeclarationKind> maybeDecl, - PossibleError* possibleError /* = nullptr */); - -template <> -bool -Parser<FullParseHandler>::checkDestructuringObject(ParseNode* objectPattern, - Maybe<DeclarationKind> maybeDecl) +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::bindingIdentifier(DeclarationKind kind, YieldHandling yieldHandling) { - MOZ_ASSERT(objectPattern->isKind(PNK_OBJECT)); + RootedPropertyName name(context, bindingIdentifier(yieldHandling)); + if (!name) + return null(); - for (ParseNode* member = objectPattern->pn_head; member; member = member->pn_next) { - ParseNode* target; - if (member->isKind(PNK_MUTATEPROTO)) { - target = member->pn_kid; - } else { - MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND)); - MOZ_ASSERT_IF(member->isKind(PNK_SHORTHAND), - member->pn_left->isKind(PNK_OBJECT_PROPERTY_NAME) && - member->pn_right->isKind(PNK_NAME) && - member->pn_left->pn_atom == member->pn_right->pn_atom); + Node binding = newName(name); + if (!binding || !noteDeclaredName(name, kind, pos())) + return null(); - target = member->pn_right; - } - if (handler.isUnparenthesizedAssignment(target)) - target = target->pn_left; + return binding; +} - if (handler.isUnparenthesizedDestructuringPattern(target)) { - if (!checkDestructuringPattern(target, maybeDecl)) - return false; - } else { - if (!checkDestructuringName(target, maybeDecl)) - return false; - } +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::bindingIdentifierOrPattern(DeclarationKind kind, YieldHandling yieldHandling, + TokenKind tt) +{ + if (tt == TOK_LB) + return arrayBindingPattern(kind, yieldHandling); + + if (tt == TOK_LC) + return objectBindingPattern(kind, yieldHandling); + + if (!TokenKindIsPossibleIdentifierName(tt)) { + error(JSMSG_NO_VARIABLE_NAME); + return null(); } - return true; + return bindingIdentifier(kind, yieldHandling); } -template <> -bool -Parser<FullParseHandler>::checkDestructuringArray(ParseNode* arrayPattern, - Maybe<DeclarationKind> maybeDecl) +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::objectBindingPattern(DeclarationKind kind, YieldHandling yieldHandling) { - MOZ_ASSERT(arrayPattern->isKind(PNK_ARRAY)); + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); - for (ParseNode* element = arrayPattern->pn_head; element; element = element->pn_next) { - if (element->isKind(PNK_ELISION)) - continue; + JS_CHECK_RECURSION(context, return null()); - ParseNode* target; - if (element->isKind(PNK_SPREAD)) { - if (element->pn_next) { - errorAt(element->pn_next->pn_pos.begin, JSMSG_PARAMETER_AFTER_REST); - return false; - } - target = element->pn_kid; - } else if (handler.isUnparenthesizedAssignment(element)) { - target = element->pn_left; + uint32_t begin = pos().begin; + Node literal = handler.newObjectLiteral(begin); + if (!literal) + return null(); + + Maybe<DeclarationKind> declKind = Some(kind); + RootedAtom propAtom(context); + for (;;) { + TokenKind tt; + if (!tokenStream.peekToken(&tt)) + return null(); + if (tt == TOK_RC) + break; + + if (tt == TOK_TRIPLEDOT) { + // rest-binding property + tokenStream.consumeKnownToken(TOK_TRIPLEDOT); + uint32_t begin = pos().begin; + + TokenKind tt; + if (!tokenStream.getToken(&tt)) + return null(); + + Node inner = bindingIdentifierOrPattern(kind, yieldHandling, tt); + if (!inner) + return null(); + + if (!handler.addSpreadProperty(literal, begin, inner)) + return null(); } else { - target = element; + TokenPos namePos = tokenStream.nextToken().pos; + + PropertyType propType; + Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom); + if (!propName) + return null(); + if (propType == PropertyType::Normal) { + // Handle e.g., |var {p: x} = o| and |var {p: x=0} = o|. + + if (!tokenStream.getToken(&tt, TokenStream::Operand)) + return null(); + + Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt); + if (!binding) + return null(); + + bool hasInitializer; + if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN)) + return null(); + + Node bindingExpr = hasInitializer + ? bindingInitializer(binding, kind, yieldHandling) + : binding; + if (!bindingExpr) + return null(); + + if (!handler.addPropertyDefinition(literal, propName, bindingExpr)) + return null(); + } else if (propType == PropertyType::Shorthand) { + // Handle e.g., |var {x, y} = o| as destructuring shorthand + // for |var {x: x, y: y} = o|. + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt)); + + Node binding = bindingIdentifier(kind, yieldHandling); + if (!binding) + return null(); + + if (!handler.addShorthand(literal, propName, binding)) + return null(); + } else if (propType == PropertyType::CoverInitializedName) { + // Handle e.g., |var {x=1, y=2} = o| as destructuring + // shorthand with default values. + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt)); + + Node binding = bindingIdentifier(kind, yieldHandling); + if (!binding) + return null(); + + tokenStream.consumeKnownToken(TOK_ASSIGN); + + Node bindingExpr = bindingInitializer(binding, kind, yieldHandling); + if (!bindingExpr) + return null(); + + if (!handler.addPropertyDefinition(literal, propName, bindingExpr)) + return null(); + } else { + errorAt(namePos.begin, JSMSG_NO_VARIABLE_NAME); + return null(); + + } } - if (handler.isUnparenthesizedDestructuringPattern(target)) { - if (!checkDestructuringPattern(target, maybeDecl)) - return false; - } else { - if (!checkDestructuringName(target, maybeDecl)) - return false; + bool matched; + if (!tokenStream.matchToken(&matched, TOK_COMMA)) + return null(); + if (!matched) + break; + if (tt == TOK_TRIPLEDOT) { + error(JSMSG_REST_WITH_COMMA); + return null(); } } - return true; + MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::None, + reportMissingClosing(JSMSG_CURLY_AFTER_LIST, + JSMSG_CURLY_OPENED, begin)); + + handler.setEndPosition(literal, pos().end); + return literal; } -/* - * Destructuring patterns can appear in two kinds of contexts: - * - * - assignment-like: assignment expressions and |for| loop heads. In - * these cases, the patterns' property value positions can be - * arbitrary lvalue expressions; the destructuring is just a fancy - * assignment. - * - * - binding-like: |var| and |let| declarations, functions' formal - * parameter lists, |catch| clauses, and comprehension tails. In - * these cases, the patterns' property value positions must be - * simple names; the destructuring defines them as new variables. - * - * In both cases, other code parses the pattern as an arbitrary - * primaryExpr, and then, here in checkDestructuringPattern, verify - * that the tree is a valid AssignmentPattern or BindingPattern. - * - * In assignment-like contexts, we parse the pattern with - * pc->inDestructuringDecl clear, so the lvalue expressions in the - * pattern are parsed normally. primaryExpr links variable references - * into the appropriate use chains; creates placeholder definitions; - * and so on. checkDestructuringPattern won't bind any new names and - * we specialize lvalues as appropriate. - * - * In declaration-like contexts, the normal variable reference - * processing would just be an obstruction, because we're going to - * define the names that appear in the property value positions as new - * variables anyway. In this case, we parse the pattern with - * pc->inDestructuringDecl set, which directs primaryExpr to leave - * whatever name nodes it creates unconnected. Then, here in - * checkDestructuringPattern, we require the pattern's property value - * positions to be simple names, and define them as appropriate to the - * context. - */ -template <> -bool -Parser<FullParseHandler>::checkDestructuringPattern(ParseNode* pattern, - Maybe<DeclarationKind> maybeDecl, - PossibleError* possibleError /* = nullptr */) +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::arrayBindingPattern(DeclarationKind kind, YieldHandling yieldHandling) { - if (pattern->isKind(PNK_ARRAYCOMP)) { - errorAt(pattern->pn_pos.begin, JSMSG_ARRAY_COMP_LEFTSIDE); - return false; - } + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LB)); - bool isDestructuring = pattern->isKind(PNK_ARRAY) - ? checkDestructuringArray(pattern, maybeDecl) - : checkDestructuringObject(pattern, maybeDecl); + JS_CHECK_RECURSION(context, return null()); - // Report any pending destructuring error. - if (isDestructuring && possibleError && !possibleError->checkForDestructuringError()) - return false; + uint32_t begin = pos().begin; + Node literal = handler.newArrayLiteral(begin); + if (!literal) + return null(); - return isDestructuring; -} + uint32_t index = 0; + TokenStream::Modifier modifier = TokenStream::Operand; + for (; ; index++) { + if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) { + error(JSMSG_ARRAY_INIT_TOO_BIG); + return null(); + } + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::Operand)) + return null(); + + if (tt == TOK_RB) { + tokenStream.ungetToken(); + break; + } + + if (tt == TOK_COMMA) { + if (!handler.addElision(literal, pos())) + return null(); + } else if (tt == TOK_TRIPLEDOT) { + uint32_t begin = pos().begin; + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::Operand)) + return null(); + + Node inner = bindingIdentifierOrPattern(kind, yieldHandling, tt); + if (!inner) + return null(); + + if (!handler.addSpreadElement(literal, begin, inner)) + return null(); + } else { + Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt); + if (!binding) + return null(); + + bool hasInitializer; + if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN)) + return null(); + + Node element = hasInitializer + ? bindingInitializer(binding, kind, yieldHandling) + : binding; + if (!element) + return null(); + + handler.addArrayElement(literal, element); + } + + if (tt != TOK_COMMA) { + // If we didn't already match TOK_COMMA in above case. + bool matched; + if (!tokenStream.matchToken(&matched, TOK_COMMA)) + return null(); + if (!matched) { + modifier = TokenStream::None; + break; + } + if (tt == TOK_TRIPLEDOT) { + error(JSMSG_REST_WITH_COMMA); + return null(); + } + } + } + + MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RB, modifier, + reportMissingClosing(JSMSG_BRACKET_AFTER_LIST, + JSMSG_BRACKET_OPENED, begin)); -template <> -bool -Parser<SyntaxParseHandler>::checkDestructuringPattern(Node pattern, - Maybe<DeclarationKind> maybeDecl, - PossibleError* possibleError /* = nullptr */) -{ - return abortIfSyntaxParser(); + handler.setEndPosition(literal, pos().end); + return literal; } template <typename ParseHandler> @@ -4262,18 +4569,9 @@ Parser<ParseHandler>::destructuringDeclaration(DeclarationKind kind, YieldHandli MOZ_ASSERT(tokenStream.isCurrentTokenType(tt)); MOZ_ASSERT(tt == TOK_LB || tt == TOK_LC); - PossibleError possibleError(*this); - Node pattern; - { - pc->inDestructuringDecl = Some(kind); - pattern = primaryExpr(yieldHandling, TripledotProhibited, tt, &possibleError); - pc->inDestructuringDecl = Nothing(); - } - - if (!pattern || !checkDestructuringPattern(pattern, Some(kind), &possibleError)) - return null(); - - return pattern; + return tt == TOK_LB + ? arrayBindingPattern(kind, yieldHandling) + : objectBindingPattern(kind, yieldHandling); } template <typename ParseHandler> @@ -4303,6 +4601,7 @@ typename ParseHandler::Node Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling, unsigned errorNumber) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); + uint32_t openedPos = pos().begin; ParseContext::Statement stmt(pc, StatementKind::Block); ParseContext::Scope scope(this); @@ -4313,7 +4612,9 @@ Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling, unsigned error if (!list) return null(); - MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, errorNumber); + MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand, + reportMissingClosing(errorNumber, JSMSG_CURLY_OPENED, + openedPos)); return finishLexicalScope(scope, list); } @@ -4384,6 +4685,12 @@ Parser<ParseHandler>::declarationPattern(Node decl, DeclarationKind declKind, To // binary operator (examined with modifier None) terminated |init|. // For all other declarations, through ASI's infinite majesty, a next // token on a new line would begin an expression. + // Similar to the case in initializerInNameDeclaration(), we need to + // peek at the next token when assignExpr() is a lazily parsed arrow + // function. + TokenKind ignored; + if (!tokenStream.peekToken(&ignored)) + return null(); tokenStream.addModifierException(TokenStream::OperandIsNone); } @@ -4484,7 +4791,8 @@ Parser<ParseHandler>::declarationName(Node decl, DeclarationKind declKind, Token ParseNodeKind* forHeadKind, Node* forInOrOfExpression) { // Anything other than TOK_YIELD or TOK_NAME is an error. - if (tt != TOK_NAME && tt != TOK_YIELD) { + // Anything other than possible identifier is an error. + if (!TokenKindIsPossibleIdentifier(tt)) { error(JSMSG_NO_VARIABLE_NAME); return null(); } @@ -4625,8 +4933,10 @@ Parser<ParseHandler>::declarationList(YieldHandling yieldHandling, template <typename ParseHandler> typename ParseHandler::Node -Parser<ParseHandler>::lexicalDeclaration(YieldHandling yieldHandling, bool isConst) +Parser<ParseHandler>::lexicalDeclaration(YieldHandling yieldHandling, DeclarationKind kind) { + MOZ_ASSERT(kind == DeclarationKind::Const || kind == DeclarationKind::Let); + /* * Parse body-level lets without a new block object. ES6 specs * that an execution environment's initial lexical environment @@ -4638,8 +4948,9 @@ Parser<ParseHandler>::lexicalDeclaration(YieldHandling yieldHandling, bool isCon * * See 8.1.1.1.6 and the note in 13.2.1. */ - Node decl = declarationList(yieldHandling, isConst ? PNK_CONST : PNK_LET); - if (!decl || !MatchOrInsertSemicolonAfterExpression(tokenStream)) + Node decl = declarationList(yieldHandling, + kind == DeclarationKind::Const ? PNK_CONST : PNK_LET); + if (!decl || !matchOrInsertSemicolonAfterExpression()) return null(); return decl; @@ -4650,41 +4961,34 @@ bool Parser<FullParseHandler>::namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet) { if (tt == TOK_LC) { - TokenStream::Modifier modifier = TokenStream::KeywordIsName; while (true) { // Handle the forms |import {} from 'a'| and // |import { ..., } from 'a'| (where ... is non empty), by // escaping the loop early if the next token is }. - if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName)) + if (!tokenStream.getToken(&tt)) return false; if (tt == TOK_RC) break; - // If the next token is a keyword, the previous call to - // peekToken matched it as a TOK_NAME, and put it in the - // lookahead buffer, so this call will match keywords as well. - MUST_MATCH_TOKEN_MOD(TOK_NAME, TokenStream::KeywordIsName, JSMSG_NO_IMPORT_NAME); + if (!TokenKindIsPossibleIdentifierName(tt)) { + error(JSMSG_NO_IMPORT_NAME); + return false; + } + Rooted<PropertyName*> importName(context, tokenStream.currentName()); TokenPos importNamePos = pos(); - TokenKind maybeAs; - if (!tokenStream.peekToken(&maybeAs)) + bool matched; + if (!tokenStream.matchToken(&matched, TOK_AS)) return null(); - if (maybeAs == TOK_NAME && - tokenStream.nextName() == context->names().as) - { - tokenStream.consumeKnownToken(TOK_NAME); - - if (!checkUnescapedName()) - return false; - + if (matched) { TokenKind afterAs; if (!tokenStream.getToken(&afterAs)) return false; - if (afterAs != TOK_NAME && afterAs != TOK_YIELD) { + if (!TokenKindIsPossibleIdentifierName(afterAs)) { error(JSMSG_NO_BINDING_NAME); return false; } @@ -4694,10 +4998,7 @@ Parser<FullParseHandler>::namedImportsOrNamespaceImport(TokenKind tt, Node impor // by the keyword 'as'. // See the ImportSpecifier production in ES6 section 15.2.2. if (IsKeyword(importName)) { - JSAutoByteString bytes; - if (!AtomToPrintableString(context, importName, &bytes)) - return false; - error(JSMSG_AS_AFTER_RESERVED_WORD, bytes.ptr()); + error(JSMSG_AS_AFTER_RESERVED_WORD, ReservedWordToCharZ(importName)); return false; } } @@ -4722,31 +5023,24 @@ Parser<FullParseHandler>::namedImportsOrNamespaceImport(TokenKind tt, Node impor handler.addList(importSpecSet, importSpec); - bool matched; - if (!tokenStream.matchToken(&matched, TOK_COMMA)) + TokenKind next; + if (!tokenStream.getToken(&next)) return false; - if (!matched) { - modifier = TokenStream::None; + if (next == TOK_RC) break; + + if (next != TOK_COMMA) { + error(JSMSG_RC_AFTER_IMPORT_SPEC_LIST); + return false; } } - - MUST_MATCH_TOKEN_MOD(TOK_RC, modifier, JSMSG_RC_AFTER_IMPORT_SPEC_LIST); } else { MOZ_ASSERT(tt == TOK_MUL); - if (!tokenStream.getToken(&tt)) - return false; - if (tt != TOK_NAME || tokenStream.currentName() != context->names().as) { - error(JSMSG_AS_AFTER_IMPORT_STAR); - return false; - } - - if (!checkUnescapedName()) - return false; + MUST_MATCH_TOKEN(TOK_AS, JSMSG_AS_AFTER_IMPORT_STAR); - MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME); + MUST_MATCH_TOKEN_FUNC(TokenKindIsPossibleIdentifierName, JSMSG_NO_BINDING_NAME); Node importName = newName(context->names().star); if (!importName) @@ -4780,14 +5074,6 @@ Parser<FullParseHandler>::namedImportsOrNamespaceImport(TokenKind tt, Node impor } template<> -bool -Parser<SyntaxParseHandler>::namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet) -{ - MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); - return false; -} - -template<> ParseNode* Parser<FullParseHandler>::importDeclaration() { @@ -4807,8 +5093,15 @@ Parser<FullParseHandler>::importDeclaration() if (!importSpecSet) return null(); - if (tt == TOK_NAME || tt == TOK_LC || tt == TOK_MUL) { - if (tt == TOK_NAME) { + if (tt == TOK_STRING) { + // Handle the form |import 'a'| by leaving the list empty. This is + // equivalent to |import {} from 'a'|. + importSpecSet->pn_pos.end = importSpecSet->pn_pos.begin; + } else { + if (tt == TOK_LC || tt == TOK_MUL) { + if (!namedImportsOrNamespaceImport(tt, importSpecSet)) + return null(); + } else if (TokenKindIsPossibleIdentifierName(tt)) { // Handle the form |import a from 'b'|, by adding a single import // specifier to the list, with 'default' as the import name and // 'a' as the binding name. This is equivalent to @@ -4851,36 +5144,20 @@ Parser<FullParseHandler>::importDeclaration() return null(); } } else { - if (!namedImportsOrNamespaceImport(tt, importSpecSet)) - return null(); - } - - if (!tokenStream.getToken(&tt)) - return null(); - - if (tt != TOK_NAME || tokenStream.currentName() != context->names().from) { - error(JSMSG_FROM_AFTER_IMPORT_CLAUSE); + error(JSMSG_DECLARATION_AFTER_IMPORT); return null(); } - if (!checkUnescapedName()) - return null(); + MUST_MATCH_TOKEN(TOK_FROM, JSMSG_FROM_AFTER_IMPORT_CLAUSE); MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); - } else if (tt == TOK_STRING) { - // Handle the form |import 'a'| by leaving the list empty. This is - // equivalent to |import {} from 'a'|. - importSpecSet->pn_pos.end = importSpecSet->pn_pos.begin; - } else { - error(JSMSG_DECLARATION_AFTER_IMPORT); - return null(); } Node moduleSpec = stringLiteral(); if (!moduleSpec) return null(); - if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) + if (!matchOrInsertSemicolonAfterNonExpression()) return null(); ParseNode* node = @@ -4947,284 +5224,539 @@ Parser<SyntaxParseHandler>::checkExportedNamesForDeclaration(Node node) } template<> -ParseNode* -Parser<FullParseHandler>::exportDeclaration() +bool +Parser<FullParseHandler>::checkExportedNameForClause(ParseNode* node) { - MOZ_ASSERT(tokenStream.currentToken().type == TOK_EXPORT); + return checkExportedName(node->pn_atom); +} - if (!pc->atModuleLevel()) { - error(JSMSG_EXPORT_DECL_AT_TOP_LEVEL); +template<> +bool +Parser<SyntaxParseHandler>::checkExportedNameForClause(Node node) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template<> +bool +Parser<FullParseHandler>::checkExportedNameForFunction(ParseNode* node) +{ + return checkExportedName(node->pn_funbox->function()->explicitName()); +} + +template<> +bool +Parser<SyntaxParseHandler>::checkExportedNameForFunction(Node node) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template<> +bool +Parser<FullParseHandler>::checkExportedNameForClass(ParseNode* node) +{ + const ClassNode& cls = node->as<ClassNode>(); + MOZ_ASSERT(cls.names()); + return checkExportedName(cls.names()->innerBinding()->pn_atom); +} + +template<> +bool +Parser<SyntaxParseHandler>::checkExportedNameForClass(Node node) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template<> +bool +Parser<FullParseHandler>::processExport(ParseNode* node) +{ + return pc->sc()->asModuleContext()->builder.processExport(node); +} + +template<> +bool +Parser<SyntaxParseHandler>::processExport(Node node) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template<> +bool +Parser<FullParseHandler>::processExportFrom(ParseNode* node) +{ + return pc->sc()->asModuleContext()->builder.processExportFrom(node); +} + +template<> +bool +Parser<SyntaxParseHandler>::processExportFrom(Node node) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportFrom(uint32_t begin, Node specList) +{ + if (!abortIfSyntaxParser()) return null(); - } - uint32_t begin = pos().begin; + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FROM)); - Node kid; - TokenKind tt; - if (!tokenStream.getToken(&tt)) + if (!abortIfSyntaxParser()) return null(); - switch (tt) { - case TOK_LC: { - kid = handler.newList(PNK_EXPORT_SPEC_LIST); - if (!kid) - return null(); - while (true) { - // Handle the forms |export {}| and |export { ..., }| (where ... - // is non empty), by escaping the loop early if the next token - // is }. - if (!tokenStream.peekToken(&tt)) - return null(); - if (tt == TOK_RC) - break; + MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); - MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME); - Node bindingName = newName(tokenStream.currentName()); - if (!bindingName) - return null(); + Node moduleSpec = stringLiteral(); + if (!moduleSpec) + return null(); - bool foundAs; - if (!tokenStream.matchContextualKeyword(&foundAs, context->names().as)) - return null(); - if (foundAs) - MUST_MATCH_TOKEN_MOD(TOK_NAME, TokenStream::KeywordIsName, JSMSG_NO_EXPORT_NAME); + if (!matchOrInsertSemicolonAfterNonExpression()) + return null(); - Node exportName = newName(tokenStream.currentName()); - if (!exportName) - return null(); + Node node = handler.newExportFromDeclaration(begin, specList, moduleSpec); + if (!node) + return null(); - if (!checkExportedName(exportName->pn_atom)) - return null(); + if (!processExportFrom(node)) + return null(); - Node exportSpec = handler.newBinary(PNK_EXPORT_SPEC, bindingName, exportName); - if (!exportSpec) - return null(); + return node; +} - handler.addList(kid, exportSpec); +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportBatch(uint32_t begin) +{ + if (!abortIfSyntaxParser()) + return null(); - bool matched; - if (!tokenStream.matchToken(&matched, TOK_COMMA)) - return null(); - if (!matched) - break; - } + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_MUL)); - MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_EXPORT_SPEC_LIST); + Node kid = handler.newList(PNK_EXPORT_SPEC_LIST); + if (!kid) + return null(); - // Careful! If |from| follows, even on a new line, it must start a - // FromClause: - // - // export { x } - // from "foo"; // a single ExportDeclaration - // - // But if it doesn't, we might have an ASI opportunity in Operand - // context, so simply matching a contextual keyword won't work: - // - // export { x } // ExportDeclaration, terminated by ASI - // fro\u006D // ExpressionStatement, the name "from" - // - // In that case let MatchOrInsertSemicolonAfterNonExpression sort out - // ASI or any necessary error. - TokenKind tt; - if (!tokenStream.getToken(&tt, TokenStream::Operand)) - return null(); + // Handle the form |export *| by adding a special export batch + // specifier to the list. + Node exportSpec = handler.newNullary(PNK_EXPORT_BATCH_SPEC, JSOP_NOP, pos()); + if (!exportSpec) + return null(); - if (tt == TOK_NAME && - tokenStream.currentToken().name() == context->names().from && - !tokenStream.currentToken().nameContainsEscape()) - { - MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); + handler.addList(kid, exportSpec); - Node moduleSpec = stringLiteral(); - if (!moduleSpec) - return null(); + MUST_MATCH_TOKEN(TOK_FROM, JSMSG_FROM_AFTER_EXPORT_STAR); - if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) - return null(); + return exportFrom(begin, kid); +} - ParseNode* node = handler.newExportFromDeclaration(begin, kid, moduleSpec); - if (!node || !pc->sc()->asModuleContext()->builder.processExportFrom(node)) - return null(); +template<> +bool +Parser<FullParseHandler>::checkLocalExportNames(ParseNode* node) +{ + // ES 2017 draft 15.2.3.1. + for (ParseNode* next = node->pn_head; next; next = next->pn_next) { + ParseNode* name = next->pn_left; + MOZ_ASSERT(name->isKind(PNK_NAME)); - return node; - } + RootedPropertyName ident(context, name->pn_atom->asPropertyName()); + if (!checkLocalExportName(ident, name->pn_pos.begin)) + return false; + } - tokenStream.ungetToken(); + return true; +} - if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) - return null(); - break; - } +template<> +bool +Parser<SyntaxParseHandler>::checkLocalExportNames(Node node) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} - case TOK_MUL: { - kid = handler.newList(PNK_EXPORT_SPEC_LIST); - if (!kid) - return null(); +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportClause(uint32_t begin) +{ + if (!abortIfSyntaxParser()) + return null(); - // Handle the form |export *| by adding a special export batch - // specifier to the list. - Node exportSpec = handler.newNullary(PNK_EXPORT_BATCH_SPEC, JSOP_NOP, pos()); - if (!exportSpec) - return null(); + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); - handler.addList(kid, exportSpec); + Node kid = handler.newList(PNK_EXPORT_SPEC_LIST); + if (!kid) + return null(); + TokenKind tt; + while (true) { + // Handle the forms |export {}| and |export { ..., }| (where ... is non + // empty), by escaping the loop early if the next token is }. if (!tokenStream.getToken(&tt)) return null(); - if (tt != TOK_NAME || tokenStream.currentName() != context->names().from) { - error(JSMSG_FROM_AFTER_EXPORT_STAR); + + if (tt == TOK_RC) + break; + + if (!TokenKindIsPossibleIdentifierName(tt)) { + error(JSMSG_NO_BINDING_NAME); return null(); } - if (!checkUnescapedName()) + Node bindingName = newName(tokenStream.currentName()); + if (!bindingName) return null(); - MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); - - Node moduleSpec = stringLiteral(); - if (!moduleSpec) + bool foundAs; + if (!tokenStream.matchToken(&foundAs, TOK_AS)) return null(); + if (foundAs) + MUST_MATCH_TOKEN_FUNC(TokenKindIsPossibleIdentifierName, JSMSG_NO_EXPORT_NAME); - if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) + Node exportName = newName(tokenStream.currentName()); + if (!exportName) return null(); - ParseNode* node = handler.newExportFromDeclaration(begin, kid, moduleSpec); - if (!node || !pc->sc()->asModuleContext()->builder.processExportFrom(node)) + if (!checkExportedNameForClause(exportName)) return null(); - return node; + Node exportSpec = handler.newBinary(PNK_EXPORT_SPEC, bindingName, exportName); + if (!exportSpec) + return null(); - } + handler.addList(kid, exportSpec); - case TOK_FUNCTION: - kid = functionStmt(pos().begin, YieldIsKeyword, NameRequired); - if (!kid) + TokenKind next; + if (!tokenStream.getToken(&next)) return null(); - if (!checkExportedName(kid->pn_funbox->function()->explicitName())) - return null(); - break; + if (next == TOK_RC) + break; - case TOK_CLASS: { - kid = classDefinition(YieldIsKeyword, ClassStatement, NameRequired); - if (!kid) + if (next != TOK_COMMA) { + error(JSMSG_RC_AFTER_EXPORT_SPEC_LIST); return null(); + } + } - const ClassNode& cls = kid->as<ClassNode>(); - MOZ_ASSERT(cls.names()); - if (!checkExportedName(cls.names()->innerBinding()->pn_atom)) - return null(); - break; - } + // Careful! If |from| follows, even on a new line, it must start a + // FromClause: + // + // export { x } + // from "foo"; // a single ExportDeclaration + // + // But if it doesn't, we might have an ASI opportunity in Operand context: + // + // export { x } // ExportDeclaration, terminated by ASI + // fro\u006D // ExpressionStatement, the name "from" + // + // In that case let matchOrInsertSemicolonAfterNonExpression sort out ASI + // or any necessary error. + bool matched; + if (!tokenStream.matchToken(&matched, TOK_FROM, TokenStream::Operand)) + return null(); - case TOK_VAR: - kid = declarationList(YieldIsName, PNK_VAR); - if (!kid) - return null(); - if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) - return null(); - if (!checkExportedNamesForDeclaration(kid)) - return null(); - break; + if (matched) + return exportFrom(begin, kid); - case TOK_DEFAULT: { - if (!tokenStream.getToken(&tt, TokenStream::Operand)) - return null(); + if (!matchOrInsertSemicolonAfterNonExpression()) + return null(); - if (!checkExportedName(context->names().default_)) - return null(); + if (!checkLocalExportNames(kid)) + return null(); - ParseNode* nameNode = nullptr; - switch (tt) { - case TOK_FUNCTION: - kid = functionStmt(pos().begin, YieldIsKeyword, AllowDefaultName); - if (!kid) - return null(); - break; - case TOK_CLASS: - kid = classDefinition(YieldIsKeyword, ClassStatement, AllowDefaultName); - if (!kid) - return null(); - break; - default: { - if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) { - TokenKind nextSameLine = TOK_EOF; - if (!tokenStream.peekTokenSameLine(&nextSameLine)) - return null(); + Node node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end)); + if (!node) + return null(); - if (nextSameLine == TOK_FUNCTION) { - tokenStream.consumeKnownToken(nextSameLine); - kid = functionStmt(pos().begin, YieldIsName, AllowDefaultName, AsyncFunction); - if (!kid) - return null(); - break; - } - } + if (!processExport(node)) + return null(); - tokenStream.ungetToken(); - RootedPropertyName name(context, context->names().starDefaultStar); - nameNode = newName(name); - if (!nameNode) - return null(); - if (!noteDeclaredName(name, DeclarationKind::Const, pos())) - return null(); - kid = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited); - if (!kid) - return null(); - if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) - return null(); - break; - } - } + return node; +} - ParseNode* node = handler.newExportDefaultDeclaration(kid, nameNode, - TokenPos(begin, pos().end)); - if (!node || !pc->sc()->asModuleContext()->builder.processExport(node)) - return null(); +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportVariableStatement(uint32_t begin) +{ + if (!abortIfSyntaxParser()) + return null(); - return node; - } + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_VAR)); - case TOK_CONST: - kid = lexicalDeclaration(YieldIsName, /* isConst = */ true); - if (!kid) - return null(); - if (!checkExportedNamesForDeclaration(kid)) - return null(); - break; + Node kid = declarationList(YieldIsName, PNK_VAR); + if (!kid) + return null(); + if (!matchOrInsertSemicolonAfterExpression()) + return null(); + if (!checkExportedNamesForDeclaration(kid)) + return null(); - case TOK_NAME: - if (tokenStream.currentName() == context->names().let) { - if (!checkUnescapedName()) - return null(); + Node node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end)); + if (!node) + return null(); - kid = lexicalDeclaration(YieldIsName, /* isConst = */ false); - if (!kid) - return null(); - if (!checkExportedNamesForDeclaration(kid)) - return null(); - break; - } - MOZ_FALLTHROUGH; + if (!processExport(node)) + return null(); - default: - error(JSMSG_DECLARATION_AFTER_EXPORT); + return node; +} + +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportFunctionDeclaration(uint32_t begin) +{ + if (!abortIfSyntaxParser()) + return null(); + + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); + + Node kid = functionStmt(pos().begin, YieldIsKeyword, NameRequired); + if (!kid) + return null(); + + if (!checkExportedNameForFunction(kid)) return null(); - } - ParseNode* node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end)); - if (!node || !pc->sc()->asModuleContext()->builder.processExport(node)) + Node node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end)); + if (!node) + return null(); + + if (!processExport(node)) return null(); return node; } -template<> -SyntaxParseHandler::Node -Parser<SyntaxParseHandler>::exportDeclaration() +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportClassDeclaration(uint32_t begin) { - JS_ALWAYS_FALSE(abortIfSyntaxParser()); - return SyntaxParseHandler::NodeFailure; + if (!abortIfSyntaxParser()) + return null(); + + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS)); + + Node kid = classDefinition(YieldIsKeyword, ClassStatement, NameRequired); + if (!kid) + return null(); + + if (!checkExportedNameForClass(kid)) + return null(); + + Node node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end)); + if (!node) + return null(); + + if (!processExport(node)) + return null(); + + return node; +} + +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportLexicalDeclaration(uint32_t begin, DeclarationKind kind) +{ + if (!abortIfSyntaxParser()) + return null(); + + MOZ_ASSERT(kind == DeclarationKind::Const || kind == DeclarationKind::Let); + MOZ_ASSERT_IF(kind == DeclarationKind::Const, tokenStream.isCurrentTokenType(TOK_CONST)); + MOZ_ASSERT_IF(kind == DeclarationKind::Let, tokenStream.isCurrentTokenType(TOK_LET)); + + Node kid = lexicalDeclaration(YieldIsName, kind); + if (!kid) + return null(); + if (!checkExportedNamesForDeclaration(kid)) + return null(); + + Node node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end)); + if (!node) + return null(); + + if (!processExport(node)) + return null(); + + return node; +} + +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportDefaultFunctionDeclaration(uint32_t begin, + FunctionAsyncKind asyncKind + /* = SyncFunction */) +{ + if (!abortIfSyntaxParser()) + return null(); + + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); + + Node kid = functionStmt(pos().begin, YieldIsKeyword, AllowDefaultName, asyncKind); + if (!kid) + return null(); + + Node node = handler.newExportDefaultDeclaration(kid, null(), TokenPos(begin, pos().end)); + if (!node) + return null(); + + if (!processExport(node)) + return null(); + + return node; +} + +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportDefaultClassDeclaration(uint32_t begin) +{ + if (!abortIfSyntaxParser()) + return null(); + + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS)); + + Node kid = classDefinition(YieldIsKeyword, ClassStatement, AllowDefaultName); + if (!kid) + return null(); + + Node node = handler.newExportDefaultDeclaration(kid, null(), TokenPos(begin, pos().end)); + if (!node) + return null(); + + if (!processExport(node)) + return null(); + + return node; +} + +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportDefaultAssignExpr(uint32_t begin) +{ + if (!abortIfSyntaxParser()) + return null(); + + RootedPropertyName name(context, context->names().starDefaultStar); + Node nameNode = newName(name); + if (!nameNode) + return null(); + if (!noteDeclaredName(name, DeclarationKind::Const, pos())) + return null(); + + Node kid = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited); + if (!kid) + return null(); + if (!matchOrInsertSemicolonAfterExpression()) + return null(); + + Node node = handler.newExportDefaultDeclaration(kid, nameNode, TokenPos(begin, pos().end)); + if (!node) + return null(); + + if (!processExport(node)) + return null(); + + return node; +} + +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportDefault(uint32_t begin) +{ + if (!abortIfSyntaxParser()) + return null(); + + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_DEFAULT)); + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::Operand)) + return null(); + + if (!checkExportedName(context->names().default_)) + return null(); + + switch (tt) { + case TOK_FUNCTION: + return exportDefaultFunctionDeclaration(begin); + + case TOK_ASYNC: { + TokenKind nextSameLine = TOK_EOF; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) + return null(); + + if (nextSameLine == TOK_FUNCTION) { + tokenStream.consumeKnownToken(TOK_FUNCTION); + return exportDefaultFunctionDeclaration(begin, AsyncFunction); + } + + tokenStream.ungetToken(); + return exportDefaultAssignExpr(begin); + } + + case TOK_CLASS: + return exportDefaultClassDeclaration(begin); + + default: + tokenStream.ungetToken(); + return exportDefaultAssignExpr(begin); + } +} + +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::exportDeclaration() +{ + if (!abortIfSyntaxParser()) + return null(); + + MOZ_ASSERT(tokenStream.currentToken().type == TOK_EXPORT); + + if (!pc->atModuleLevel()) { + error(JSMSG_EXPORT_DECL_AT_TOP_LEVEL); + return null(); + } + + uint32_t begin = pos().begin; + + TokenKind tt; + if (!tokenStream.getToken(&tt)) + return null(); + switch (tt) { + case TOK_MUL: + return exportBatch(begin); + + case TOK_LC: + return exportClause(begin); + + case TOK_VAR: + return exportVariableStatement(begin); + + case TOK_FUNCTION: + return exportFunctionDeclaration(begin); + + case TOK_CLASS: + return exportClassDeclaration(begin); + + case TOK_CONST: + return exportLexicalDeclaration(begin, DeclarationKind::Const); + + case TOK_LET: + return exportLexicalDeclaration(begin, DeclarationKind::Let); + + case TOK_DEFAULT: + return exportDefault(begin); + + default: + error(JSMSG_DECLARATION_AFTER_EXPORT); + return null(); + } } template <typename ParseHandler> @@ -5236,7 +5768,7 @@ Parser<ParseHandler>::expressionStatement(YieldHandling yieldHandling, InvokedPr /* possibleError = */ nullptr, invoked); if (!pnexpr) return null(); - if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) + if (!matchOrInsertSemicolonAfterExpression()) return null(); return handler.newExprStatement(pnexpr, pos().end); } @@ -5249,14 +5781,71 @@ Parser<ParseHandler>::consequentOrAlternative(YieldHandling yieldHandling) if (!tokenStream.peekToken(&next, TokenStream::Operand)) return null(); - if (next == TOK_FUNCTION) { - // Apply Annex B.3.4 in non-strict code to allow FunctionDeclaration as - // the consequent/alternative of an |if| or |else|. Parser::statement - // will report the strict mode error. - if (!pc->sc()->strict()) { - tokenStream.consumeKnownToken(next, TokenStream::Operand); - return functionStmt(pos().begin, yieldHandling, NameRequired); + // Annex B.3.4 says that unbraced FunctionDeclarations under if/else in + // non-strict code act as if they were braced: |if (x) function f() {}| + // parses as |if (x) { function f() {} }|. + // + // Careful! FunctionDeclaration doesn't include generators or async + // functions. + if (next == TOK_ASYNC) { + tokenStream.consumeKnownToken(next, TokenStream::Operand); + + // Peek only on the same line: ExpressionStatement's lookahead + // restriction is phrased as + // + // [lookahead ∉ { {, function, async [no LineTerminator here] function, class, let [ }] + // + // meaning that code like this is valid: + // + // if (true) + // async // ASI opportunity + // function clownshoes() {} + TokenKind maybeFunction; + if (!tokenStream.peekTokenSameLine(&maybeFunction)) + return null(); + + if (maybeFunction == TOK_FUNCTION) { + error(JSMSG_FORBIDDEN_AS_STATEMENT, "async function declarations"); + return null(); } + + // Otherwise this |async| begins an ExpressionStatement. + tokenStream.ungetToken(); + } else if (next == TOK_FUNCTION) { + tokenStream.consumeKnownToken(next, TokenStream::Operand); + + // Parser::statement would handle this, but as this function handles + // every other error case, it seems best to handle this. + if (pc->sc()->strict()) { + error(JSMSG_FORBIDDEN_AS_STATEMENT, "function declarations"); + return null(); + } + + TokenKind maybeStar; + if (!tokenStream.peekToken(&maybeStar)) + return null(); + + if (maybeStar == TOK_MUL) { + error(JSMSG_FORBIDDEN_AS_STATEMENT, "generator declarations"); + return null(); + } + + ParseContext::Statement stmt(pc, StatementKind::Block); + ParseContext::Scope scope(this); + if (!scope.init(pc)) + return null(); + + TokenPos funcPos = pos(); + Node fun = functionStmt(pos().begin, yieldHandling, NameRequired); + if (!fun) + return null(); + + Node block = handler.newStatementList(funcPos); + if (!block) + return null(); + + handler.addStatementToList(block, fun); + return finishLexicalScope(scope, block); } return statement(yieldHandling); @@ -5371,13 +5960,9 @@ Parser<ParseHandler>::matchInOrOf(bool* isForInp, bool* isForOfp) return false; *isForInp = tt == TOK_IN; - *isForOfp = tt == TOK_NAME && tokenStream.currentToken().name() == context->names().of; - if (!*isForInp && !*isForOfp) { + *isForOfp = tt == TOK_OF; + if (!*isForInp && !*isForOfp) tokenStream.ungetToken(); - } else { - if (tt == TOK_NAME && !checkUnescapedName()) - return false; - } MOZ_ASSERT_IF(*isForInp || *isForOfp, *isForInp != *isForOfp); return true; @@ -5427,15 +6012,12 @@ Parser<ParseHandler>::forHeadStart(YieldHandling yieldHandling, if (tt == TOK_CONST) { parsingLexicalDeclaration = true; tokenStream.consumeKnownToken(tt, TokenStream::Operand); - } else if (tt == TOK_NAME && - tokenStream.nextName() == context->names().let && - !tokenStream.nextNameContainsEscape()) - { + } else if (tt == TOK_LET) { // We could have a {For,Lexical}Declaration, or we could have a // LeftHandSideExpression with lookahead restrictions so it's not // ambiguous with the former. Check for a continuation of the former // to decide which we have. - tokenStream.consumeKnownToken(TOK_NAME, TokenStream::Operand); + tokenStream.consumeKnownToken(TOK_LET, TokenStream::Operand); TokenKind next; if (!tokenStream.peekToken(&next)) @@ -5512,7 +6094,7 @@ Parser<ParseHandler>::forHeadStart(YieldHandling yieldHandling, // Verify the left-hand side expression doesn't have a forbidden form. if (handler.isUnparenthesizedDestructuringPattern(*forInitialPart)) { - if (!checkDestructuringPattern(*forInitialPart, Nothing(), &possibleError)) + if (!possibleError.checkForDestructuringErrorOrWarning()) return false; } else if (handler.isNameAnyParentheses(*forInitialPart)) { const char* chars = handler.nameIsArgumentsEvalAnyParentheses(*forInitialPart, context); @@ -5556,7 +6138,7 @@ Parser<ParseHandler>::forStatement(YieldHandling yieldHandling) if (allowsForEachIn()) { bool matched; - if (!tokenStream.matchContextualKeyword(&matched, context->names().each)) + if (!tokenStream.matchToken(&matched, TOK_EACH)) return null(); if (matched) { iflags = JSITER_FOREACH; @@ -5867,7 +6449,7 @@ Parser<ParseHandler>::continueStatement(YieldHandling yieldHandling) return null(); } - if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) + if (!matchOrInsertSemicolonAfterNonExpression()) return null(); return handler.newContinueStatement(label, TokenPos(begin, pos().end)); @@ -5907,7 +6489,7 @@ Parser<ParseHandler>::breakStatement(YieldHandling yieldHandling) } } - if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) + if (!matchOrInsertSemicolonAfterNonExpression()) return null(); return handler.newBreakStatement(label, TokenPos(begin, pos().end)); @@ -5947,10 +6529,10 @@ Parser<ParseHandler>::returnStatement(YieldHandling yieldHandling) } if (exprNode) { - if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) + if (!matchOrInsertSemicolonAfterExpression()) return null(); } else { - if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) + if (!matchOrInsertSemicolonAfterNonExpression()) return null(); } @@ -6070,7 +6652,7 @@ Parser<ParseHandler>::yieldExpression(InHandling inHandling) if (pc->funHasReturnExpr #if JS_HAS_EXPR_CLOSURES - || pc->functionBox()->function()->isExprBody() + || pc->functionBox()->isExprBody() #endif ) { @@ -6247,7 +6829,7 @@ Parser<ParseHandler>::throwStatement(YieldHandling yieldHandling) if (!throwExpr) return null(); - if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) + if (!matchOrInsertSemicolonAfterExpression()) return null(); return handler.newThrowStatement(throwExpr, TokenPos(begin, pos().end)); @@ -6267,12 +6849,12 @@ Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling) * kid3 is the finally statement * * catch nodes are ternary. - * kid1 is the lvalue (TOK_NAME, TOK_LB, or TOK_LC) + * kid1 is the lvalue (possible identifier, TOK_LB, or TOK_LC) * kid2 is the catch guard or null if no guard * kid3 is the catch block * * catch lvalue nodes are either: - * TOK_NAME for a single identifier + * a single identifier * TOK_RB or TOK_RC for a destructuring left-hand side * * finally nodes are TOK_LC statement lists. @@ -6282,6 +6864,8 @@ Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling) { MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY); + uint32_t openedPos = pos().begin; + ParseContext::Statement stmt(pc, StatementKind::Try); ParseContext::Scope scope(this); if (!scope.init(pc)) @@ -6295,7 +6879,9 @@ Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling) if (!innerBlock) return null(); - MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_TRY); + MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand, + reportMissingClosing(JSMSG_CURLY_AFTER_TRY, + JSMSG_CURLY_OPENED, openedPos)); } bool hasUnconditionalCatch = false; @@ -6347,22 +6933,18 @@ Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling) return null(); break; - case TOK_NAME: - case TOK_YIELD: { - RootedPropertyName param(context, bindingIdentifier(yieldHandling)); - if (!param) + default: { + if (!TokenKindIsPossibleIdentifierName(tt)) { + error(JSMSG_CATCH_IDENTIFIER); return null(); - catchName = newName(param); + } + + catchName = bindingIdentifier(DeclarationKind::SimpleCatchParameter, + yieldHandling); if (!catchName) return null(); - if (!noteDeclaredName(param, DeclarationKind::SimpleCatchParameter, pos())) - return null(); break; } - - default: - error(JSMSG_CATCH_IDENTIFIER); - return null(); } Node catchGuard = null(); @@ -6411,6 +6993,8 @@ Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling) if (tt == TOK_FINALLY) { MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY); + uint32_t openedPos = pos().begin; + ParseContext::Statement stmt(pc, StatementKind::Finally); ParseContext::Scope scope(this); if (!scope.init(pc)) @@ -6424,7 +7008,9 @@ Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling) if (!finallyBlock) return null(); - MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_FINALLY); + MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand, + reportMissingClosing(JSMSG_CURLY_AFTER_FINALLY, + JSMSG_CURLY_OPENED, openedPos)); } else { tokenStream.ungetToken(); } @@ -6441,6 +7027,8 @@ typename ParseHandler::Node Parser<ParseHandler>::catchBlockStatement(YieldHandling yieldHandling, ParseContext::Scope& catchParamScope) { + uint32_t openedPos = pos().begin; + ParseContext::Statement stmt(pc, StatementKind::Block); // ES 13.15.7 CatchClauseEvaluation @@ -6460,7 +7048,9 @@ Parser<ParseHandler>::catchBlockStatement(YieldHandling yieldHandling, if (!list) return null(); - MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_CATCH); + MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand, + reportMissingClosing(JSMSG_CURLY_AFTER_CATCH, + JSMSG_CURLY_OPENED, openedPos)); // The catch parameter names are not bound in the body scope, so remove // them before generating bindings. @@ -6474,7 +7064,7 @@ Parser<ParseHandler>::debuggerStatement() { TokenPos p; p.begin = pos().begin; - if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream)) + if (!matchOrInsertSemicolonAfterNonExpression()) return null(); p.end = pos().end; @@ -6514,6 +7104,7 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS)); + uint32_t classStartOffset = pos().begin; bool savedStrictness = setLocalStrictMode(true); TokenKind tt; @@ -6521,7 +7112,7 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, return null(); RootedPropertyName name(context); - if (tt == TOK_NAME || tt == TOK_YIELD) { + if (TokenKindIsPossibleIdentifier(tt)) { name = bindingIdentifier(yieldHandling); if (!name) return null(); @@ -6539,16 +7130,20 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, tokenStream.ungetToken(); } + // Push a ParseContext::ClassStatement to keep track of the constructor + // funbox. + ParseContext::ClassStatement classStmt(pc); + RootedAtom propAtom(context); // A named class creates a new lexical scope with a const binding of the - // class name. - Maybe<ParseContext::Statement> classStmt; - Maybe<ParseContext::Scope> classScope; + // class name for the "inner name". + Maybe<ParseContext::Statement> innerScopeStmt; + Maybe<ParseContext::Scope> innerScope; if (name) { - classStmt.emplace(pc, StatementKind::Block); - classScope.emplace(this); - if (!classScope->init(pc)) + innerScopeStmt.emplace(pc, StatementKind::Block); + innerScope.emplace(this); + if (!innerScope->init(pc)) return null(); } @@ -6575,10 +7170,10 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, if (!classMethods) return null(); - bool seenConstructor = false; + Maybe<DeclarationKind> declKind = Nothing(); for (;;) { TokenKind tt; - if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName)) + if (!tokenStream.getToken(&tt)) return null(); if (tt == TOK_RC) break; @@ -6587,22 +7182,18 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, continue; bool isStatic = false; - if (tt == TOK_NAME && tokenStream.currentName() == context->names().static_) { - if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName)) + if (tt == TOK_STATIC) { + if (!tokenStream.peekToken(&tt)) return null(); if (tt == TOK_RC) { - tokenStream.consumeKnownToken(tt, TokenStream::KeywordIsName); + tokenStream.consumeKnownToken(tt); error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(tt)); return null(); } if (tt != TOK_LP) { - if (!checkUnescapedName()) - return null(); - isStatic = true; } else { - tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName); tokenStream.ungetToken(); } } else { @@ -6614,7 +7205,7 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, return null(); PropertyType propType; - Node propName = propertyName(yieldHandling, classMethods, &propType, &propAtom); + Node propName = propertyName(yieldHandling, declKind, classMethods, &propType, &propAtom); if (!propName) return null(); @@ -6631,16 +7222,17 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, propType = PropertyType::GetterNoExpressionClosure; if (propType == PropertyType::Setter) propType = PropertyType::SetterNoExpressionClosure; - if (!isStatic && propAtom == context->names().constructor) { + + bool isConstructor = !isStatic && propAtom == context->names().constructor; + if (isConstructor) { if (propType != PropertyType::Method) { errorAt(nameOffset, JSMSG_BAD_METHOD_DEF); return null(); } - if (seenConstructor) { + if (classStmt.constructorBox) { errorAt(nameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor"); return null(); } - seenConstructor = true; propType = hasHeritage ? PropertyType::DerivedConstructor : PropertyType::Constructor; } else if (isStatic && propAtom == context->names().prototype) { errorAt(nameOffset, JSMSG_BAD_METHOD_DEF); @@ -6665,7 +7257,12 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, if (!tokenStream.isCurrentTokenType(TOK_RB)) funName = propAtom; } - Node fn = methodDefinition(nameOffset, propType, funName); + + // Calling toString on constructors need to return the source text for + // the entire class. The end offset is unknown at this point in + // parsing and will be amended when class parsing finishes below. + Node fn = methodDefinition(isConstructor ? classStartOffset : nameOffset, + propType, funName); if (!fn) return null(); @@ -6676,6 +7273,15 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, return null(); } + // Amend the toStringEnd offset for the constructor now that we've + // finished parsing the class. + uint32_t classEndOffset = pos().end; + if (FunctionBox* ctorbox = classStmt.constructorBox) { + if (ctorbox->function()->isInterpretedLazy()) + ctorbox->function()->lazyScript()->setToStringEnd(classEndOffset); + ctorbox->toStringEnd = classEndOffset; + } + Node nameNode = null(); Node methodsOrBlock = classMethods; if (name) { @@ -6687,15 +7293,15 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, if (!innerName) return null(); - Node classBlock = finishLexicalScope(*classScope, classMethods); + Node classBlock = finishLexicalScope(*innerScope, classMethods); if (!classBlock) return null(); methodsOrBlock = classBlock; // Pop the inner scope. - classScope.reset(); - classStmt.reset(); + innerScope.reset(); + innerScopeStmt.reset(); Node outerName = null(); if (classContext == ClassStatement) { @@ -6715,16 +7321,15 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness)); - return handler.newClass(nameNode, classHeritage, methodsOrBlock); + return handler.newClass(nameNode, classHeritage, methodsOrBlock, + TokenPos(classStartOffset, classEndOffset)); } template <class ParseHandler> bool Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling) { - MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME)); - MOZ_ASSERT(tokenStream.currentName() == context->names().let); - MOZ_ASSERT(!tokenStream.currentToken().nameContainsEscape()); + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LET)); #ifdef DEBUG TokenKind verify; @@ -6736,18 +7341,19 @@ Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHand if (next == TOK_LB || next == TOK_LC) return true; - // Otherwise a let declaration must have a name. - if (next == TOK_NAME) { - if (tokenStream.nextName() == context->names().yield) { - MOZ_ASSERT(tokenStream.nextNameContainsEscape(), - "token stream should interpret unescaped 'yield' as TOK_YIELD"); - - // Same as |next == TOK_YIELD|. - return yieldHandling == YieldIsName; - } + // If we have the name "yield", the grammar parameter exactly states + // whether this is okay. (This wasn't true for SpiderMonkey's ancient + // legacy generator syntax, but that's dead now.) If YieldIsName, + // declaration-parsing code will (if necessary) enforce a strict mode + // restriction on defining "yield". If YieldIsKeyword, consider this the + // end of the declaration, in case ASI induces a semicolon that makes the + // "yield" valid. + if (next == TOK_YIELD) + return yieldHandling == YieldIsName; - // One non-"yield" TOK_NAME edge case deserves special comment. - // Consider this: + // Otherwise a let declaration must have a name. + if (TokenKindIsPossibleIdentifier(next)) { + // A "let" edge case deserves special comment. Consider this: // // let // not an ASI opportunity // let; @@ -6760,16 +7366,6 @@ Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHand return true; } - // If we have the name "yield", the grammar parameter exactly states - // whether this is okay. (This wasn't true for SpiderMonkey's ancient - // legacy generator syntax, but that's dead now.) If YieldIsName, - // declaration-parsing code will (if necessary) enforce a strict mode - // restriction on defining "yield". If YieldIsKeyword, consider this the - // end of the declaration, in case ASI induces a semicolon that makes the - // "yield" valid. - if (next == TOK_YIELD) - return yieldHandling == YieldIsName; - // Otherwise not a let declaration. return false; } @@ -6781,7 +7377,7 @@ Parser<ParseHandler>::variableStatement(YieldHandling yieldHandling) Node vars = declarationList(yieldHandling, PNK_VAR); if (!vars) return null(); - if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) + if (!matchOrInsertSemicolonAfterExpression()) return null(); return vars; } @@ -6832,16 +7428,21 @@ Parser<ParseHandler>::statement(YieldHandling yieldHandling) return expressionStatement(yieldHandling); } - case TOK_NAME: { + default: { + // Avoid getting next token with None. + if (tt == TOK_AWAIT && pc->isAsync()) + return expressionStatement(yieldHandling); + + if (!TokenKindIsPossibleIdentifier(tt)) + return expressionStatement(yieldHandling); + TokenKind next; if (!tokenStream.peekToken(&next)) return null(); // |let| here can only be an Identifier, not a declaration. Give nicer // errors for declaration-looking typos. - if (!tokenStream.currentToken().nameContainsEscape() && - tokenStream.currentName() == context->names().let) - { + if (tt == TOK_LET) { bool forbiddenLetDeclaration = false; if (pc->sc()->strict() || versionNumber() >= JSVERSION_1_7) { @@ -6851,7 +7452,7 @@ Parser<ParseHandler>::statement(YieldHandling yieldHandling) } else if (next == TOK_LB) { // Enforce ExpressionStatement's 'let [' lookahead restriction. forbiddenLetDeclaration = true; - } else if (next == TOK_LC || next == TOK_NAME) { + } else if (next == TOK_LC || TokenKindIsPossibleIdentifier(next)) { // 'let {' and 'let foo' aren't completely forbidden, if ASI // causes 'let' to be the entire Statement. But if they're // same-line, we can aggressively give a better error message. @@ -6862,7 +7463,7 @@ Parser<ParseHandler>::statement(YieldHandling yieldHandling) if (!tokenStream.peekTokenSameLine(&nextSameLine)) return null(); - MOZ_ASSERT(nextSameLine == TOK_NAME || + MOZ_ASSERT(TokenKindIsPossibleIdentifier(nextSameLine) || nextSameLine == TOK_LC || nextSameLine == TOK_EOL); @@ -6886,9 +7487,6 @@ Parser<ParseHandler>::statement(YieldHandling yieldHandling) case TOK_NEW: return expressionStatement(yieldHandling, PredictInvoked); - default: - return expressionStatement(yieldHandling); - // IfStatement[?Yield, ?Return] case TOK_IF: return ifStatement(yieldHandling); @@ -6934,7 +7532,7 @@ Parser<ParseHandler>::statement(YieldHandling yieldHandling) return withStatement(yieldHandling); // LabelledStatement[?Yield, ?Return] - // This is really handled by TOK_NAME and TOK_YIELD cases above. + // This is really handled by default and TOK_YIELD cases above. // ThrowStatement[?Yield] case TOK_THROW: @@ -7040,26 +7638,29 @@ Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling, return expressionStatement(yieldHandling); } - case TOK_NAME: { + default: { + // Avoid getting next token with None. + if (tt == TOK_AWAIT && pc->isAsync()) + return expressionStatement(yieldHandling); + + if (!TokenKindIsPossibleIdentifier(tt)) + return expressionStatement(yieldHandling); + TokenKind next; if (!tokenStream.peekToken(&next)) return null(); - if (!tokenStream.currentToken().nameContainsEscape() && - tokenStream.currentName() == context->names().let && - nextTokenContinuesLetDeclaration(next, yieldHandling)) - { - return lexicalDeclaration(yieldHandling, /* isConst = */ false); - } + if (tt == TOK_LET && nextTokenContinuesLetDeclaration(next, yieldHandling)) + return lexicalDeclaration(yieldHandling, DeclarationKind::Let); - if (tokenStream.currentName() == context->names().async) { + if (tt == TOK_ASYNC) { TokenKind nextSameLine = TOK_EOF; if (!tokenStream.peekTokenSameLine(&nextSameLine)) return null(); if (nextSameLine == TOK_FUNCTION) { - uint32_t preludeStart = pos().begin; + uint32_t toStringStart = pos().begin; tokenStream.consumeKnownToken(TOK_FUNCTION); - return functionStmt(preludeStart, yieldHandling, NameRequired, AsyncFunction); + return functionStmt(toStringStart, yieldHandling, NameRequired, AsyncFunction); } } @@ -7072,9 +7673,6 @@ Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling, case TOK_NEW: return expressionStatement(yieldHandling, PredictInvoked); - default: - return expressionStatement(yieldHandling); - // IfStatement[?Yield, ?Return] case TOK_IF: return ifStatement(yieldHandling); @@ -7120,7 +7718,7 @@ Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling, return withStatement(yieldHandling); // LabelledStatement[?Yield, ?Return] - // This is really handled by TOK_NAME and TOK_YIELD cases above. + // This is really handled by default and TOK_YIELD cases above. // ThrowStatement[?Yield] case TOK_THROW: @@ -7149,7 +7747,7 @@ Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling, case TOK_CONST: // [In] is the default behavior, because for-loops specially parse // their heads to handle |in| in this situation. - return lexicalDeclaration(yieldHandling, /* isConst = */ true); + return lexicalDeclaration(yieldHandling, DeclarationKind::Const); // ImportDeclaration (only inside modules) case TOK_IMPORT: @@ -7438,26 +8036,6 @@ Parser<ParseHandler>::condExpr1(InHandling inHandling, YieldHandling yieldHandli return handler.newConditional(condition, thenExpr, elseExpr); } -class AutoClearInDestructuringDecl -{ - ParseContext* pc_; - Maybe<DeclarationKind> saved_; - - public: - explicit AutoClearInDestructuringDecl(ParseContext* pc) - : pc_(pc), - saved_(pc->inDestructuringDecl) - { - pc->inDestructuringDecl = Nothing(); - if (saved_ && *saved_ == DeclarationKind::FormalParameter) - pc->functionBox()->hasParameterExprs = true; - } - - ~AutoClearInDestructuringDecl() { - pc_->inDestructuringDecl = saved_; - } -}; - template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandling, @@ -7482,10 +8060,13 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl if (!tokenStream.getToken(&tt, TokenStream::Operand)) return null(); - uint32_t exprOffset = pos().begin; + TokenPos exprPos = pos(); bool endsExpr; + // This only handles identifiers that *never* have special meaning anywhere + // in the language. Contextual keywords, reserved words in strict mode, + // and other hard cases are handled outside this fast path. if (tt == TOK_NAME) { if (!tokenStream.nextTokenEndsExpr(&endsExpr)) return null(); @@ -7516,12 +8097,12 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl return yieldExpression(inHandling); bool maybeAsyncArrow = false; - if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) { + if (tt == TOK_ASYNC) { TokenKind nextSameLine = TOK_EOF; if (!tokenStream.peekTokenSameLine(&nextSameLine)) return null(); - if (nextSameLine == TOK_NAME || nextSameLine == TOK_YIELD) + if (TokenKindIsPossibleIdentifier(nextSameLine)) maybeAsyncArrow = true; } @@ -7535,13 +8116,12 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl PossibleError possibleErrorInner(*this); Node lhs; if (maybeAsyncArrow) { - tokenStream.consumeKnownToken(TOK_NAME, TokenStream::Operand); - MOZ_ASSERT(tokenStream.currentName() == context->names().async); + tokenStream.consumeKnownToken(TOK_ASYNC, TokenStream::Operand); TokenKind tt; if (!tokenStream.getToken(&tt)) return null(); - MOZ_ASSERT(tt == TOK_NAME || tt == TOK_YIELD); + MOZ_ASSERT(TokenKindIsPossibleIdentifier(tt)); // Check yield validity here. RootedPropertyName name(context, bindingIdentifier(yieldHandling)); @@ -7604,28 +8184,24 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl if (!tokenStream.getToken(&next, TokenStream::Operand)) return null(); - uint32_t preludeStart = pos().begin; + uint32_t toStringStart = pos().begin; tokenStream.ungetToken(); GeneratorKind generatorKind = NotGenerator; FunctionAsyncKind asyncKind = SyncFunction; - if (next == TOK_NAME) { + if (next == TOK_ASYNC) { tokenStream.consumeKnownToken(next, TokenStream::Operand); - if (tokenStream.currentName() == context->names().async) { - TokenKind nextSameLine = TOK_EOF; - if (!tokenStream.peekTokenSameLine(&nextSameLine)) - return null(); + TokenKind nextSameLine = TOK_EOF; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) + return null(); - if (nextSameLine == TOK_ARROW) { - tokenStream.ungetToken(); - } else { - generatorKind = StarGenerator; - asyncKind = AsyncFunction; - } - } else { + if (nextSameLine == TOK_ARROW) { tokenStream.ungetToken(); + } else { + generatorKind = StarGenerator; + asyncKind = AsyncFunction; } } @@ -7633,7 +8209,7 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl if (!pn) return null(); - Node arrowFunc = functionDefinition(preludeStart, pn, inHandling, yieldHandling, nullptr, + Node arrowFunc = functionDefinition(toStringStart, pn, inHandling, yieldHandling, nullptr, Arrow, generatorKind, asyncKind); if (!arrowFunc) return null(); @@ -7690,12 +8266,12 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl return null(); } - if (!checkDestructuringPattern(lhs, Nothing(), &possibleErrorInner)) + if (!possibleErrorInner.checkForDestructuringErrorOrWarning()) return null(); } else if (handler.isNameAnyParentheses(lhs)) { if (const char* chars = handler.nameIsArgumentsEvalAnyParentheses(lhs, context)) { // |chars| is "arguments" or "eval" here. - if (!strictModeErrorAt(exprOffset, JSMSG_BAD_STRICT_ASSIGN, chars)) + if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_STRICT_ASSIGN, chars)) return null(); } @@ -7703,23 +8279,22 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl } else if (handler.isPropertyAccess(lhs)) { // Permitted: no additional testing/fixup needed. } else if (handler.isFunctionCall(lhs)) { - if (!strictModeErrorAt(exprOffset, JSMSG_BAD_LEFTSIDE_OF_ASS)) + if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS)) return null(); + + if (possibleError) + possibleError->setPendingDestructuringErrorAt(exprPos, JSMSG_BAD_DESTRUCT_TARGET); } else { - errorAt(exprOffset, JSMSG_BAD_LEFTSIDE_OF_ASS); + errorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS); return null(); } if (!possibleErrorInner.checkForExpressionError()) return null(); - Node rhs; - { - AutoClearInDestructuringDecl autoClear(pc); - rhs = assignExpr(inHandling, yieldHandling, TripledotProhibited); - if (!rhs) - return null(); - } + Node rhs = assignExpr(inHandling, yieldHandling, TripledotProhibited); + if (!rhs) + return null(); if (kind == PNK_ASSIGN) handler.checkAndSetIsDirectRHSAnonFunction(rhs); @@ -7874,20 +8449,17 @@ Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling t } case TOK_AWAIT: { - if (!pc->isAsync()) { - // TOK_AWAIT can be returned in module, even if it's not inside - // async function. - error(JSMSG_RESERVED_ID, "await"); - return null(); + if (pc->isAsync()) { + Node kid = unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked); + if (!kid) + return null(); + pc->lastAwaitOffset = begin; + return newAwaitExpression(begin, kid); } - - Node kid = unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked); - if (!kid) - return null(); - pc->lastAwaitOffset = begin; - return newAwaitExpression(begin, kid); } + MOZ_FALLTHROUGH; + default: { Node expr = memberExpr(yieldHandling, tripledotHandling, tt, /* allowCallSyntax = */ true, possibleError, invoked); @@ -7949,7 +8521,7 @@ Parser<ParseHandler>::generatorComprehensionLambda(unsigned begin) // Create box for fun->object early to root it. Directives directives(/* strict = */ outerpc->sc()->strict()); - FunctionBox* genFunbox = newFunctionBox(genfn, fun, /* preludeStart = */ 0, directives, + FunctionBox* genFunbox = newFunctionBox(genfn, fun, /* toStringStart = */ 0, directives, StarGenerator, SyncFunction, /* tryAnnexB = */ false); if (!genFunbox) return null(); @@ -7985,7 +8557,7 @@ Parser<ParseHandler>::generatorComprehensionLambda(unsigned begin) uint32_t end = pos().end; handler.setBeginPosition(comp, begin); handler.setEndPosition(comp, end); - genFunbox->bufEnd = end; + genFunbox->setEnd(end); handler.addStatementToList(body, comp); handler.setEndPosition(body, end); handler.setBeginPosition(genfn, begin); @@ -8025,8 +8597,10 @@ Parser<ParseHandler>::comprehensionFor(GeneratorKind comprehensionKind) // FIXME: Destructuring binding (bug 980828). - MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_VARIABLE_NAME); - RootedPropertyName name(context, tokenStream.currentName()); + MUST_MATCH_TOKEN_FUNC(TokenKindIsPossibleIdentifier, JSMSG_NO_VARIABLE_NAME); + RootedPropertyName name(context, bindingIdentifier(YieldIsKeyword)); + if (!name) + return null(); if (name == context->names().let) { error(JSMSG_LET_COMP_BINDING); return null(); @@ -8036,7 +8610,7 @@ Parser<ParseHandler>::comprehensionFor(GeneratorKind comprehensionKind) if (!lhs) return null(); bool matched; - if (!tokenStream.matchContextualKeyword(&matched, context->names().of)) + if (!tokenStream.matchToken(&matched, TOK_OF)) return null(); if (!matched) { error(JSMSG_OF_AFTER_FOR_NAME); @@ -8371,9 +8945,9 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling Node nextMember; if (tt == TOK_DOT) { - if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName)) + if (!tokenStream.getToken(&tt)) return null(); - if (tt == TOK_NAME) { + if (TokenKindIsPossibleIdentifierName(tt)) { PropertyName* field = tokenStream.currentName(); if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) { error(JSMSG_BAD_SUPERPROP, "property"); @@ -8448,8 +9022,27 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling JSOp op = JSOP_CALL; bool maybeAsyncArrow = false; - if (tt == TOK_LP && handler.isNameAnyParentheses(lhs)) { - if (handler.nameIsEvalAnyParentheses(lhs, context)) { + if (PropertyName* prop = handler.maybeDottedProperty(lhs)) { + // Use the JSOP_FUN{APPLY,CALL} optimizations given the + // right syntax. + if (prop == context->names().apply) { + op = JSOP_FUNAPPLY; + if (pc->isFunctionBox()) { + pc->functionBox()->usesApply = true; + } + } else if (prop == context->names().call) { + op = JSOP_FUNCALL; + } + } else if (tt == TOK_LP) { + if (handler.isAsyncKeyword(lhs, context)) { + // |async (| can be the start of an async arrow + // function, so we need to defer reporting possible + // errors from destructuring syntax. To give better + // error messages, we only allow the AsyncArrowHead + // part of the CoverCallExpressionAndAsyncArrowHead + // syntax when the initial name is "async". + maybeAsyncArrow = true; + } else if (handler.isEvalAnyParentheses(lhs, context)) { // Select the right EVAL op and flag pc as having a // direct eval. op = pc->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL; @@ -8466,24 +9059,6 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling // it. (If we're not in a method, that's fine, so // ignore the return value.) checkAndMarkSuperScope(); - } else if (handler.nameIsUnparenthesizedAsync(lhs, context)) { - // |async (| can be the start of an async arrow - // function, so we need to defer reporting possible - // errors from destructuring syntax. To give better - // error messages, we only allow the AsyncArrowHead - // part of the CoverCallExpressionAndAsyncArrowHead - // syntax when the initial name is "async". - maybeAsyncArrow = true; - } - } else if (PropertyName* prop = handler.maybeDottedProperty(lhs)) { - // Use the JSOP_FUN{APPLY,CALL} optimizations given the - // right syntax. - if (prop == context->names().apply) { - op = JSOP_FUNAPPLY; - if (pc->isFunctionBox()) - pc->functionBox()->usesApply = true; - } else if (prop == context->names().call) { - op = JSOP_FUNCALL; } } @@ -8542,106 +9117,117 @@ Parser<ParseHandler>::newName(PropertyName* name, TokenPos pos) } template <typename ParseHandler> -PropertyName* -Parser<ParseHandler>::labelOrIdentifierReference(YieldHandling yieldHandling, - bool yieldTokenizedAsName) -{ - PropertyName* ident; - bool isYield; - const Token& tok = tokenStream.currentToken(); - if (tok.type == TOK_NAME) { - MOZ_ASSERT(tok.name() != context->names().yield || - tok.nameContainsEscape() || - yieldTokenizedAsName, - "tokenizer should have treated unescaped 'yield' as TOK_YIELD"); - MOZ_ASSERT_IF(yieldTokenizedAsName, tok.name() == context->names().yield); - - ident = tok.name(); - isYield = ident == context->names().yield; - } else { - MOZ_ASSERT(tok.type == TOK_YIELD && !yieldTokenizedAsName); +bool +Parser<ParseHandler>::checkLabelOrIdentifierReference(HandlePropertyName ident, + uint32_t offset, + YieldHandling yieldHandling) +{ + if (ident == context->names().yield) { + if (yieldHandling == YieldIsKeyword || + versionNumber() >= JSVERSION_1_7) + { + errorAt(offset, JSMSG_RESERVED_ID, "yield"); + return false; + } + if (pc->sc()->needStrictChecks()) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "yield")) + return false; + } - ident = context->names().yield; - isYield = true; + return true; } - if (!isYield) { - if (pc->sc()->strict()) { - const char* badName = ident == context->names().let - ? "let" - : ident == context->names().static_ - ? "static" - : nullptr; - if (badName) { - error(JSMSG_RESERVED_ID, badName); - return nullptr; - } + if (ident == context->names().await) { + if (awaitIsKeyword()) { + errorAt(offset, JSMSG_RESERVED_ID, "await"); + return false; } - } else { - if (yieldHandling == YieldIsKeyword || - pc->sc()->strict() || - versionNumber() >= JSVERSION_1_7) - { - error(JSMSG_RESERVED_ID, "yield"); - return nullptr; + return true; + } + + if (IsKeyword(ident) || IsReservedWordLiteral(ident)) { + errorAt(offset, JSMSG_INVALID_ID, ReservedWordToCharZ(ident)); + return false; + } + + if (IsFutureReservedWord(ident)) { + errorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(ident)); + return false; + } + + if (pc->sc()->needStrictChecks()) { + if (IsStrictReservedWord(ident)) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(ident))) + return false; + return true; + } + + if (ident == context->names().let) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "let")) + return false; + return true; + } + + if (ident == context->names().static_) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "static")) + return false; + return true; } } - return ident; + return true; } template <typename ParseHandler> -PropertyName* -Parser<ParseHandler>::bindingIdentifier(YieldHandling yieldHandling) +bool +Parser<ParseHandler>::checkBindingIdentifier(HandlePropertyName ident, + uint32_t offset, + YieldHandling yieldHandling) { - PropertyName* ident; - bool isYield; - const Token& tok = tokenStream.currentToken(); - if (tok.type == TOK_NAME) { - MOZ_ASSERT(tok.name() != context->names().yield || tok.nameContainsEscape(), - "tokenizer should have treated unescaped 'yield' as TOK_YIELD"); + if (!checkLabelOrIdentifierReference(ident, offset, yieldHandling)) + return false; - ident = tok.name(); - isYield = ident == context->names().yield; - } else { - MOZ_ASSERT(tok.type == TOK_YIELD); + if (pc->sc()->needStrictChecks()) { + if (ident == context->names().arguments) { + if (!strictModeErrorAt(offset, JSMSG_BAD_STRICT_ASSIGN, "arguments")) + return false; + return true; + } - ident = context->names().yield; - isYield = true; + if (ident == context->names().eval) { + if (!strictModeErrorAt(offset, JSMSG_BAD_STRICT_ASSIGN, "eval")) + return false; + return true; + } } - if (!isYield) { - if (pc->sc()->strict()) { - const char* badName = ident == context->names().arguments - ? "arguments" - : ident == context->names().eval - ? "eval" - : nullptr; - if (badName) { - error(JSMSG_BAD_STRICT_ASSIGN, badName); - return nullptr; - } + return true; +} - badName = ident == context->names().let - ? "let" - : ident == context->names().static_ - ? "static" - : nullptr; - if (badName) { - error(JSMSG_RESERVED_ID, badName); - return nullptr; - } - } - } else { - if (yieldHandling == YieldIsKeyword || - pc->sc()->strict() || - versionNumber() >= JSVERSION_1_7) - { - error(JSMSG_RESERVED_ID, "yield"); - return nullptr; - } - } +template <typename ParseHandler> +PropertyName* +Parser<ParseHandler>::labelOrIdentifierReference(YieldHandling yieldHandling) +{ + // ES 2017 draft 12.1.1. + // StringValue of IdentifierName normalizes any Unicode escape sequences + // in IdentifierName hence such escapes cannot be used to write an + // Identifier whose code point sequence is the same as a ReservedWord. + // + // Use PropertyName* instead of TokenKind to reflect the normalization. + RootedPropertyName ident(context, tokenStream.currentName()); + if (!checkLabelOrIdentifierReference(ident, pos().begin, yieldHandling)) + return nullptr; + return ident; +} + +template <typename ParseHandler> +PropertyName* +Parser<ParseHandler>::bindingIdentifier(YieldHandling yieldHandling) +{ + RootedPropertyName ident(context, tokenStream.currentName()); + if (!checkBindingIdentifier(ident, pos().begin, yieldHandling)) + return nullptr; return ident; } @@ -8653,7 +9239,7 @@ Parser<ParseHandler>::identifierReference(Handle<PropertyName*> name) if (!pn) return null(); - if (!pc->inDestructuringDecl && !noteUsedName(name)) + if (!noteUsedName(name)) return null(); return pn; @@ -8668,8 +9254,23 @@ Parser<ParseHandler>::stringLiteral() template <typename ParseHandler> typename ParseHandler::Node -Parser<ParseHandler>::noSubstitutionTemplate() +Parser<ParseHandler>::noSubstitutionTaggedTemplate() +{ + if (tokenStream.hasInvalidTemplateEscape()) { + tokenStream.clearInvalidTemplateEscape(); + return handler.newRawUndefinedLiteral(pos()); + } + + return handler.newTemplateStringLiteral(stopStringCompression(), pos()); +} + +template <typename ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::noSubstitutionUntaggedTemplate() { + if (!tokenStream.checkForInvalidTemplateEscapeError()) + return null(); + return handler.newTemplateStringLiteral(stopStringCompression(), pos()); } @@ -8705,6 +9306,74 @@ Parser<ParseHandler>::newRegExp() } template <typename ParseHandler> +void +Parser<ParseHandler>::checkDestructuringAssignmentTarget(Node expr, TokenPos exprPos, + PossibleError* possibleError) +{ + // Return early if a pending destructuring error is already present. + if (possibleError->hasPendingDestructuringError()) + return; + + if (pc->sc()->needStrictChecks()) { + if (handler.isArgumentsAnyParentheses(expr, context)) { + if (pc->sc()->strict()) { + possibleError->setPendingDestructuringErrorAt(exprPos, + JSMSG_BAD_STRICT_ASSIGN_ARGUMENTS); + } else { + possibleError->setPendingDestructuringWarningAt(exprPos, + JSMSG_BAD_STRICT_ASSIGN_ARGUMENTS); + } + return; + } + + if (handler.isEvalAnyParentheses(expr, context)) { + if (pc->sc()->strict()) { + possibleError->setPendingDestructuringErrorAt(exprPos, + JSMSG_BAD_STRICT_ASSIGN_EVAL); + } else { + possibleError->setPendingDestructuringWarningAt(exprPos, + JSMSG_BAD_STRICT_ASSIGN_EVAL); + } + return; + } + } + + // The expression must be either a simple assignment target, i.e. a name + // or a property accessor, or a nested destructuring pattern. + if (!handler.isUnparenthesizedDestructuringPattern(expr) && + !handler.isNameAnyParentheses(expr) && + !handler.isPropertyAccess(expr)) + { + // Parentheses are forbidden around destructuring *patterns* (but + // allowed around names). Use our nicer error message for + // parenthesized, nested patterns. + if (handler.isParenthesizedDestructuringPattern(expr)) + possibleError->setPendingDestructuringErrorAt(exprPos, JSMSG_BAD_DESTRUCT_PARENS); + else + possibleError->setPendingDestructuringErrorAt(exprPos, JSMSG_BAD_DESTRUCT_TARGET); + } +} + +template <typename ParseHandler> +void +Parser<ParseHandler>::checkDestructuringAssignmentElement(Node expr, TokenPos exprPos, + PossibleError* possibleError) +{ + // ES2018 draft rev 0719f44aab93215ed9a626b2f45bd34f36916834 + // 12.15.5 Destructuring Assignment + // + // AssignmentElement[Yield, Await]: + // DestructuringAssignmentTarget[?Yield, ?Await] + // DestructuringAssignmentTarget[?Yield, ?Await] Initializer[+In, ?Yield, ?Await] + + // If |expr| is an assignment element with an initializer expression, its + // destructuring assignment target was already validated in assignExpr(). + // Otherwise we need to check that |expr| is a valid destructuring target. + if (!handler.isUnparenthesizedAssignment(expr)) + checkDestructuringAssignmentTarget(expr, exprPos, possibleError); +} + +template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError) { @@ -8753,17 +9422,29 @@ Parser<ParseHandler>::arrayInitializer(YieldHandling yieldHandling, PossibleErro } else if (tt == TOK_TRIPLEDOT) { tokenStream.consumeKnownToken(TOK_TRIPLEDOT, TokenStream::Operand); uint32_t begin = pos().begin; + + TokenPos innerPos; + if (!tokenStream.peekTokenPos(&innerPos, TokenStream::Operand)) + return null(); + Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited, possibleError); if (!inner) return null(); + if (possibleError) + checkDestructuringAssignmentTarget(inner, innerPos, possibleError); if (!handler.addSpreadElement(literal, begin, inner)) return null(); } else { + TokenPos elementPos; + if (!tokenStream.peekTokenPos(&elementPos, TokenStream::Operand)) + return null(); Node element = assignExpr(InAllowed, yieldHandling, TripledotProhibited, possibleError); if (!element) return null(); + if (possibleError) + checkDestructuringAssignmentElement(element, elementPos, possibleError); if (foldConstants && !FoldConstants(context, &element, this)) return null(); handler.addArrayElement(literal, element); @@ -8783,7 +9464,9 @@ Parser<ParseHandler>::arrayInitializer(YieldHandling yieldHandling, PossibleErro } } - MUST_MATCH_TOKEN_MOD(TOK_RB, modifier, JSMSG_BRACKET_AFTER_LIST); + MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RB, modifier, + reportMissingClosing(JSMSG_BRACKET_AFTER_LIST, + JSMSG_BRACKET_OPENED, begin)); } handler.setEndPosition(literal, pos().end); return literal; @@ -8799,11 +9482,12 @@ DoubleToAtom(ExclusiveContext* cx, double value) template <typename ParseHandler> typename ParseHandler::Node -Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, Node propList, +Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, + const Maybe<DeclarationKind>& maybeDecl, Node propList, PropertyType* propType, MutableHandleAtom propAtom) { TokenKind ltok; - if (!tokenStream.getToken(<ok, TokenStream::KeywordIsName)) + if (!tokenStream.getToken(<ok)) return null(); MOZ_ASSERT(ltok != TOK_RC, "caller should have handled TOK_RC"); @@ -8812,11 +9496,11 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, Node propList, bool isAsync = false; if (ltok == TOK_MUL) { isGenerator = true; - if (!tokenStream.getToken(<ok, TokenStream::KeywordIsName)) + if (!tokenStream.getToken(<ok)) return null(); } - if (ltok == TOK_NAME && tokenStream.currentName() == context->names().async) { + if (ltok == TOK_ASYNC) { // AsyncMethod[Yield, Await]: // async [no LineTerminator here] PropertyName[?Yield, ?Await] ... // @@ -8832,16 +9516,13 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, Node propList, // ComputedPropertyName[Yield, Await]: // [ ... TokenKind tt = TOK_EOF; - if (!tokenStream.peekTokenSameLine(&tt, TokenStream::KeywordIsName)) + if (!tokenStream.getToken(&tt)) return null(); - if (tt == TOK_STRING || tt == TOK_NUMBER || tt == TOK_LB || - tt == TOK_NAME || tt == TOK_YIELD) - { + if (tt != TOK_LP && tt != TOK_COLON && tt != TOK_RC && tt != TOK_ASSIGN) { isAsync = true; - tokenStream.consumeKnownToken(tt, TokenStream::KeywordIsName); ltok = tt; } else { - tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName); + tokenStream.ungetToken(); } } @@ -8863,46 +9544,41 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, Node propList, break; case TOK_LB: - propName = computedPropertyName(yieldHandling, propList); + propName = computedPropertyName(yieldHandling, maybeDecl, propList); if (!propName) return null(); break; - case TOK_NAME: { + default: { + if (!TokenKindIsPossibleIdentifierName(ltok)) { + error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(ltok)); + return null(); + } + propAtom.set(tokenStream.currentName()); // Do not look for accessor syntax on generators - if (isGenerator || isAsync || - !(propAtom.get() == context->names().get || - propAtom.get() == context->names().set)) - { + if (isGenerator || isAsync || !(ltok == TOK_GET || ltok == TOK_SET)) { propName = handler.newObjectLiteralPropertyName(propAtom, pos()); if (!propName) return null(); break; } - *propType = propAtom.get() == context->names().get ? PropertyType::Getter - : PropertyType::Setter; + *propType = ltok == TOK_GET ? PropertyType::Getter : PropertyType::Setter; // We have parsed |get| or |set|. Look for an accessor property // name next. TokenKind tt; - if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName)) + if (!tokenStream.peekToken(&tt)) return null(); - if (tt == TOK_NAME) { - if (!checkUnescapedName()) - return null(); - - tokenStream.consumeKnownToken(TOK_NAME, TokenStream::KeywordIsName); + if (TokenKindIsPossibleIdentifierName(tt)) { + tokenStream.consumeKnownToken(tt); propAtom.set(tokenStream.currentName()); return handler.newObjectLiteralPropertyName(propAtom, pos()); } if (tt == TOK_STRING) { - if (!checkUnescapedName()) - return null(); - - tokenStream.consumeKnownToken(TOK_STRING, TokenStream::KeywordIsName); + tokenStream.consumeKnownToken(TOK_STRING); propAtom.set(tokenStream.currentToken().atom()); @@ -8916,10 +9592,7 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, Node propList, return stringLiteral(); } if (tt == TOK_NUMBER) { - if (!checkUnescapedName()) - return null(); - - tokenStream.consumeKnownToken(TOK_NUMBER, TokenStream::KeywordIsName); + tokenStream.consumeKnownToken(TOK_NUMBER); propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number())); if (!propAtom.get()) @@ -8927,19 +9600,15 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, Node propList, return newNumber(tokenStream.currentToken()); } if (tt == TOK_LB) { - if (!checkUnescapedName()) - return null(); + tokenStream.consumeKnownToken(TOK_LB); - tokenStream.consumeKnownToken(TOK_LB, TokenStream::KeywordIsName); - - return computedPropertyName(yieldHandling, propList); + return computedPropertyName(yieldHandling, maybeDecl, propList); } // Not an accessor property after all. propName = handler.newObjectLiteralPropertyName(propAtom.get(), pos()); if (!propName) return null(); - tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName); break; } @@ -8957,10 +9626,6 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, Node propList, return null(); break; } - - default: - error(JSMSG_BAD_PROP_ID); - return null(); } TokenKind tt; @@ -8976,7 +9641,9 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, Node propList, return propName; } - if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC || tt == TOK_ASSIGN)) { + if (TokenKindIsPossibleIdentifierName(ltok) && + (tt == TOK_COMMA || tt == TOK_RC || tt == TOK_ASSIGN)) + { if (isGenerator) { error(JSMSG_BAD_PROP_ID); return null(); @@ -9005,28 +9672,25 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, Node propList, template <typename ParseHandler> typename ParseHandler::Node -Parser<ParseHandler>::computedPropertyName(YieldHandling yieldHandling, Node literal) +Parser<ParseHandler>::computedPropertyName(YieldHandling yieldHandling, + const Maybe<DeclarationKind>& maybeDecl, + Node literal) { uint32_t begin = pos().begin; - Node assignNode; - { - // Turn off the inDestructuringDecl flag when parsing computed property - // names. In short, when parsing 'let {[x + y]: z} = obj;', noteUsedName() - // should be called on x and y, but not on z. See the comment on - // Parser<>::checkDestructuringPattern() for details. - AutoClearInDestructuringDecl autoClear(pc); - assignNode = assignExpr(InAllowed, yieldHandling, TripledotProhibited); - if (!assignNode) - return null(); + if (maybeDecl) { + if (*maybeDecl == DeclarationKind::FormalParameter) + pc->functionBox()->hasParameterExprs = true; + } else { + handler.setListFlag(literal, PNX_NONCONST); } - MUST_MATCH_TOKEN(TOK_RB, JSMSG_COMP_PROP_UNTERM_EXPR); - Node propname = handler.newComputedName(assignNode, begin, pos().end); - if (!propname) + Node assignNode = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!assignNode) return null(); - handler.setListFlag(literal, PNX_NONCONST); - return propname; + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_COMP_PROP_UNTERM_EXPR); + return handler.newComputedName(assignNode, begin, pos().end); } template <typename ParseHandler> @@ -9035,200 +9699,219 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); + uint32_t openedPos = pos().begin; + Node literal = handler.newObjectLiteral(pos().begin); if (!literal) return null(); bool seenPrototypeMutation = false; bool seenCoverInitializedName = false; + Maybe<DeclarationKind> declKind = Nothing(); RootedAtom propAtom(context); for (;;) { TokenKind tt; - if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName)) + if (!tokenStream.peekToken(&tt)) return null(); if (tt == TOK_RC) break; - TokenPos namePos = pos(); - - tokenStream.ungetToken(); - - PropertyType propType; - Node propName = propertyName(yieldHandling, literal, &propType, &propAtom); - if (!propName) - return null(); + if (tt == TOK_TRIPLEDOT) { + // object spread + tokenStream.consumeKnownToken(TOK_TRIPLEDOT); + uint32_t begin = pos().begin; - if (propType == PropertyType::Normal) { - Node propExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited, - possibleError); - if (!propExpr) + TokenPos innerPos; + if (!tokenStream.peekTokenPos(&innerPos, TokenStream::Operand)) return null(); - handler.checkAndSetIsDirectRHSAnonFunction(propExpr); - - if (foldConstants && !FoldConstants(context, &propExpr, this)) + Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited, + possibleError); + if (!inner) + return null(); + if (possibleError) + checkDestructuringAssignmentTarget(inner, innerPos, possibleError); + if (!handler.addSpreadProperty(literal, begin, inner)) return null(); + } else { + TokenPos namePos = tokenStream.nextToken().pos; - if (propAtom == context->names().proto) { - if (seenPrototypeMutation) { - // Directly report the error when we're not in a - // destructuring context. - if (!possibleError) { - errorAt(namePos.begin, JSMSG_DUPLICATE_PROTO_PROPERTY); - return null(); - } + PropertyType propType; + Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom); + if (!propName) - // Otherwise delay error reporting until we've determined - // whether or not we're destructuring. - possibleError->setPendingExpressionErrorAt(namePos, - JSMSG_DUPLICATE_PROTO_PROPERTY); - } - seenPrototypeMutation = true; + return null(); - // Note: this occurs *only* if we observe TOK_COLON! Only - // __proto__: v mutates [[Prototype]]. Getters, setters, - // method/generator definitions, computed property name - // versions of all of these, and shorthands do not. - if (!handler.addPrototypeMutation(literal, namePos.begin, propExpr)) + if (propType == PropertyType::Normal) { + TokenPos exprPos; + if (!tokenStream.peekTokenPos(&exprPos, TokenStream::Operand)) return null(); - } else { - if (!handler.isConstant(propExpr)) - handler.setListFlag(literal, PNX_NONCONST); - if (!handler.addPropertyDefinition(literal, propName, propExpr)) + Node propExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited, + possibleError); + if (!propExpr) return null(); - } - } else if (propType == PropertyType::Shorthand) { - /* - * Support, e.g., |var {x, y} = o| as destructuring shorthand - * for |var {x: x, y: y} = o|, and |var o = {x, y}| as initializer - * shorthand for |var o = {x: x, y: y}|. - */ - TokenKind propToken = TOK_NAME; - if (!tokenStream.checkForKeyword(propAtom, &propToken)) - return null(); - if (propToken != TOK_NAME && propToken != TOK_YIELD) { - error(JSMSG_RESERVED_ID, TokenKindToDesc(propToken)); - return null(); - } + handler.checkAndSetIsDirectRHSAnonFunction(propExpr); - Rooted<PropertyName*> name(context, - identifierReference(yieldHandling, propToken == TOK_YIELD)); - if (!name) - return null(); + if (possibleError) + checkDestructuringAssignmentElement(propExpr, exprPos, possibleError); - Node nameExpr = identifierReference(name); - if (!nameExpr) - return null(); + if (foldConstants && !FoldConstants(context, &propExpr, this)) + return null(); - if (!handler.addShorthand(literal, propName, nameExpr)) - return null(); - } else if (propType == PropertyType::CoverInitializedName) { - /* - * Support, e.g., |var {x=1, y=2} = o| as destructuring shorthand - * with default values, as per ES6 12.14.5 - */ - TokenKind propToken = TOK_NAME; - if (!tokenStream.checkForKeyword(propAtom, &propToken)) - return null(); + if (propAtom == context->names().proto) { + if (seenPrototypeMutation) { + // Directly report the error when we're not in a + // destructuring context. + if (!possibleError) { + errorAt(namePos.begin, JSMSG_DUPLICATE_PROTO_PROPERTY); + return null(); + } - if (propToken != TOK_NAME && propToken != TOK_YIELD) { - error(JSMSG_RESERVED_ID, TokenKindToDesc(propToken)); - return null(); - } + // Otherwise delay error reporting until we've + // determined whether or not we're destructuring. + possibleError->setPendingExpressionErrorAt(namePos, + JSMSG_DUPLICATE_PROTO_PROPERTY); + } + seenPrototypeMutation = true; - Rooted<PropertyName*> name(context, - identifierReference(yieldHandling, propToken == TOK_YIELD)); - if (!name) - return null(); + // Note: this occurs *only* if we observe TOK_COLON! Only + // __proto__: v mutates [[Prototype]]. Getters, setters, + // method/generator definitions, computed property name + // versions of all of these, and shorthands do not. + if (!handler.addPrototypeMutation(literal, namePos.begin, propExpr)) + return null(); + } else { + if (!handler.isConstant(propExpr)) + handler.setListFlag(literal, PNX_NONCONST); - Node lhs = identifierReference(name); - if (!lhs) - return null(); + if (!handler.addPropertyDefinition(literal, propName, propExpr)) + return null(); + } + } else if (propType == PropertyType::Shorthand) { + /* + * Support, e.g., |({x, y} = o)| as destructuring shorthand + * for |({x: x, y: y} = o)|, and |var o = {x, y}| as + * initializer shorthand for |var o = {x: x, y: y}|. + */ + Rooted<PropertyName*> name(context, identifierReference(yieldHandling)); + if (!name) + return null(); - tokenStream.consumeKnownToken(TOK_ASSIGN); + Node nameExpr = identifierReference(name); + if (!nameExpr) + return null(); - if (!seenCoverInitializedName) { - // "shorthand default" or "CoverInitializedName" syntax is only - // valid in the case of destructuring. - seenCoverInitializedName = true; + if (possibleError) + checkDestructuringAssignmentTarget(nameExpr, namePos, possibleError); - if (!possibleError) { - // Destructuring defaults are definitely not allowed in this object literal, - // because of something the caller knows about the preceding code. - // For example, maybe the preceding token is an operator: `x + {y=z}`. - error(JSMSG_COLON_AFTER_ID); + if (!handler.addShorthand(literal, propName, nameExpr)) return null(); + } else if (propType == PropertyType::CoverInitializedName) { + /* + * Support, e.g., |({x=1, y=2} = o)| as destructuring + * shorthand with default values, as per ES6 12.14.5 + */ + Rooted<PropertyName*> name(context, identifierReference(yieldHandling)); + if (!name) + return null(); + + Node lhs = identifierReference(name); + if (!lhs) + return null(); + + tokenStream.consumeKnownToken(TOK_ASSIGN); + + if (!seenCoverInitializedName) { + // "shorthand default" or "CoverInitializedName" syntax is + // only valid in the case of destructuring. + seenCoverInitializedName = true; + + if (!possibleError) { + // Destructuring defaults are definitely not allowed + // in this object literal, because of something the + // caller knows about the preceding code. For example, + // maybe the preceding token is an operator: + // |x + {y=z}|. + error(JSMSG_COLON_AFTER_ID); + return null(); + } + + // Here we set a pending error so that later in the parse, + // once we've determined whether or not we're + // destructuring, the error can be reported or ignored + // appropriately. + possibleError->setPendingExpressionErrorAt(pos(), JSMSG_COLON_AFTER_ID); } - // Here we set a pending error so that later in the parse, once we've - // determined whether or not we're destructuring, the error can be - // reported or ignored appropriately. - possibleError->setPendingExpressionErrorAt(pos(), JSMSG_COLON_AFTER_ID); - } + if (const char* chars = handler.nameIsArgumentsEvalAnyParentheses(lhs, context)) { + // |chars| is "arguments" or "eval" here. + if (!strictModeErrorAt(namePos.begin, JSMSG_BAD_STRICT_ASSIGN, chars)) + return null(); + } - Node rhs; - { - // Clearing `inDestructuringDecl` allows name use to be noted - // in Parser::identifierReference. See bug 1255167. - AutoClearInDestructuringDecl autoClear(pc); - rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + Node rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited); if (!rhs) return null(); - } - handler.checkAndSetIsDirectRHSAnonFunction(rhs); + handler.checkAndSetIsDirectRHSAnonFunction(rhs); - Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP); - if (!propExpr) - return null(); - - if (!handler.addPropertyDefinition(literal, propName, propExpr)) - return null(); + Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP); + if (!propExpr) + return null(); - if (!abortIfSyntaxParser()) - return null(); - } else { - RootedAtom funName(context); - if (!tokenStream.isCurrentTokenType(TOK_RB)) { - funName = propAtom; + if (!handler.addPropertyDefinition(literal, propName, propExpr)) + return null(); + } else { + RootedAtom funName(context); + if (!tokenStream.isCurrentTokenType(TOK_RB)) { + funName = propAtom; - if (propType == PropertyType::Getter || propType == PropertyType::Setter) { - funName = prefixAccessorName(propType, propAtom); - if (!funName) - return null(); + if (propType == PropertyType::Getter || propType == PropertyType::Setter) { + funName = prefixAccessorName(propType, propAtom); + if (!funName) + return null(); + } } - } - Node fn = methodDefinition(namePos.begin, propType, funName); - if (!fn) - return null(); + Node fn = methodDefinition(namePos.begin, propType, funName); + if (!fn) + return null(); - handler.checkAndSetIsDirectRHSAnonFunction(fn); + handler.checkAndSetIsDirectRHSAnonFunction(fn); - JSOp op = JSOpFromPropertyType(propType); - if (!handler.addObjectMethodDefinition(literal, propName, fn, op)) - return null(); + JSOp op = JSOpFromPropertyType(propType); + if (!handler.addObjectMethodDefinition(literal, propName, fn, op)) + return null(); + + if (possibleError) { + possibleError->setPendingDestructuringErrorAt(namePos, + JSMSG_BAD_DESTRUCT_TARGET); + } + } } - if (!tokenStream.getToken(&tt)) + bool matched; + if (!tokenStream.matchToken(&matched, TOK_COMMA)) return null(); - if (tt == TOK_RC) + if (!matched) break; - if (tt != TOK_COMMA) { - error(JSMSG_CURLY_AFTER_LIST); - return null(); - } + if (tt == TOK_TRIPLEDOT && possibleError) + possibleError->setPendingDestructuringErrorAt(pos(), JSMSG_REST_WITH_COMMA); } + MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::None, + reportMissingClosing(JSMSG_CURLY_AFTER_LIST, + JSMSG_CURLY_OPENED, openedPos)); + handler.setEndPosition(literal, pos().end); return literal; } template <typename ParseHandler> typename ParseHandler::Node -Parser<ParseHandler>::methodDefinition(uint32_t preludeStart, PropertyType propType, +Parser<ParseHandler>::methodDefinition(uint32_t toStringStart, PropertyType propType, HandleAtom funName) { FunctionSyntaxKind kind; @@ -9282,7 +9965,7 @@ Parser<ParseHandler>::methodDefinition(uint32_t preludeStart, PropertyType propT if (!pn) return null(); - return functionDefinition(preludeStart, pn, InAllowed, yieldHandling, funName, + return functionDefinition(toStringStart, pn, InAllowed, yieldHandling, funName, kind, generatorKind, asyncKind); } @@ -9312,14 +9995,11 @@ Parser<ParseHandler>::tryNewTarget(Node &newTarget) if (!tokenStream.getToken(&next)) return false; - if (next != TOK_NAME || tokenStream.currentName() != context->names().target) { + if (next != TOK_TARGET) { error(JSMSG_UNEXPECTED_TOKEN, "target", TokenKindToDesc(next)); return false; } - if (!checkUnescapedName()) - return false; - if (!pc->sc()->allowNewTarget()) { errorAt(begin, JSMSG_BAD_NEWTARGET); return false; @@ -9389,7 +10069,6 @@ Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling if (!expr) return null(); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); - handler.setEndPosition(expr, pos().end); return handler.parenthesize(expr); } @@ -9397,22 +10076,26 @@ Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling return templateLiteral(yieldHandling); case TOK_NO_SUBS_TEMPLATE: - return noSubstitutionTemplate(); + return noSubstitutionUntaggedTemplate(); case TOK_STRING: return stringLiteral(); - case TOK_YIELD: - case TOK_NAME: { - if (tokenStream.currentName() == context->names().async) { + default: { + if (!TokenKindIsPossibleIdentifier(tt)) { + error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt)); + return null(); + } + + if (tt == TOK_ASYNC) { TokenKind nextSameLine = TOK_EOF; if (!tokenStream.peekTokenSameLine(&nextSameLine)) return null(); if (nextSameLine == TOK_FUNCTION) { - uint32_t preludeStart = pos().begin; + uint32_t toStringStart = pos().begin; tokenStream.consumeKnownToken(TOK_FUNCTION); - return functionExpr(preludeStart, PredictUninvoked, AsyncFunction); + return functionExpr(toStringStart, PredictUninvoked, AsyncFunction); } } @@ -9476,7 +10159,7 @@ Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling // the enclosing code is strict mode code, any of "let", "yield", // or "arguments" should be prohibited. Argument-parsing code // handles that. - if (next != TOK_NAME && next != TOK_YIELD) { + if (!TokenKindIsPossibleIdentifier(next)) { error(JSMSG_UNEXPECTED_TOKEN, "rest argument name", TokenKindToDesc(next)); return null(); } @@ -9503,10 +10186,6 @@ Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling // Return an arbitrary expression node. See case TOK_RP above. return handler.newNullLiteral(pos()); } - - default: - error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt)); - return null(); } } @@ -9520,9 +10199,8 @@ Parser<ParseHandler>::exprInParens(InHandling inHandling, YieldHandling yieldHan return expr(inHandling, yieldHandling, tripledotHandling, possibleError, PredictInvoked); } -template <typename ParseHandler> bool -Parser<ParseHandler>::warnOnceAboutExprClosure() +ParserBase::warnOnceAboutExprClosure() { #ifndef RELEASE_OR_BETA JSContext* cx = context->maybeJSContext(); @@ -9538,9 +10216,8 @@ Parser<ParseHandler>::warnOnceAboutExprClosure() return true; } -template <typename ParseHandler> bool -Parser<ParseHandler>::warnOnceAboutForEach() +ParserBase::warnOnceAboutForEach() { JSContext* cx = context->maybeJSContext(); if (!cx) diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 156a1c1b0..88d2dad18 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -85,6 +85,16 @@ class ParseContext : public Nestable<ParseContext> } }; + struct ClassStatement : public Statement + { + FunctionBox* constructorBox; + + explicit ClassStatement(ParseContext* pc) + : Statement(pc, StatementKind::Class), + constructorBox(nullptr) + { } + }; + // The intra-function scope stack. // // Tracks declared and used names within a scope. @@ -146,9 +156,9 @@ class ParseContext : public Nestable<ParseContext> } MOZ_MUST_USE bool addDeclaredName(ParseContext* pc, AddDeclaredNamePtr& p, JSAtom* name, - DeclarationKind kind) + DeclarationKind kind, uint32_t pos) { - return maybeReportOOM(pc, declared_->add(p, name, DeclaredNameInfo(kind))); + return maybeReportOOM(pc, declared_->add(p, name, DeclaredNameInfo(kind, pos))); } // Remove all VarForAnnexBLexicalFunction declarations of a certain @@ -330,17 +340,6 @@ class ParseContext : public Nestable<ParseContext> // pointer may be nullptr. Directives* newDirectives; - // Set when parsing a declaration-like destructuring pattern. This flag - // causes PrimaryExpr to create PN_NAME parse nodes for variable references - // which are not hooked into any definition's use chain, added to any tree - // context's AtomList, etc. etc. checkDestructuring will do that work - // later. - // - // The comments atop checkDestructuring explain the distinction between - // assignment-like and declaration-like destructuring patterns, and why - // they need to be treated differently. - mozilla::Maybe<DeclarationKind> inDestructuringDecl; - // Set when parsing a function and it has 'return <expr>;' bool funHasReturnExpr; @@ -432,6 +431,11 @@ class ParseContext : public Nestable<ParseContext> return Statement::findNearest<T>(innermostStatement_, predicate); } + template <typename T> + T* findInnermostStatement() { + return Statement::findNearest<T>(innermostStatement_); + } + AtomVector& positionalFormalParameterNames() { return *positionalFormalParameterNames_; } @@ -532,6 +536,13 @@ ParseContext::Statement::is<ParseContext::LabelStatement>() const return kind_ == StatementKind::Label; } +template <> +inline bool +ParseContext::Statement::is<ParseContext::ClassStatement>() const +{ + return kind_ == StatementKind::Class; +} + template <typename T> inline T& ParseContext::Statement::as() @@ -735,7 +746,155 @@ class UsedNameTracker }; template <typename ParseHandler> -class Parser final : private JS::AutoGCRooter, public StrictModeGetter +class AutoAwaitIsKeyword; + +class ParserBase : public StrictModeGetter +{ + private: + ParserBase* thisForCtor() { return this; } + + public: + ExclusiveContext* const context; + + LifoAlloc& alloc; + + TokenStream tokenStream; + LifoAlloc::Mark tempPoolMark; + + /* list of parsed objects for GC tracing */ + ObjectBox* traceListHead; + + /* innermost parse context (stack-allocated) */ + ParseContext* pc; + + // For tracking used names in this parsing session. + UsedNameTracker& usedNames; + + /* Compression token for aborting. */ + SourceCompressionTask* sct; + + ScriptSource* ss; + + /* Root atoms and objects allocated for the parsed tree. */ + AutoKeepAtoms keepAtoms; + + /* Perform constant-folding; must be true when interfacing with the emitter. */ + const bool foldConstants:1; + + protected: +#if DEBUG + /* Our fallible 'checkOptions' member function has been called. */ + bool checkOptionsCalled:1; +#endif + + /* + * Not all language constructs can be handled during syntax parsing. If it + * is not known whether the parse succeeds or fails, this bit is set and + * the parse will return false. + */ + bool abortedSyntaxParse:1; + + /* Unexpected end of input, i.e. TOK_EOF not at top-level. */ + bool isUnexpectedEOF_:1; + + bool awaitIsKeyword_:1; + + public: + bool awaitIsKeyword() const { + return awaitIsKeyword_; + } + + ParserBase(ExclusiveContext* cx, LifoAlloc& alloc, const ReadOnlyCompileOptions& options, + const char16_t* chars, size_t length, bool foldConstants, + UsedNameTracker& usedNames, Parser<SyntaxParseHandler>* syntaxParser, + LazyScript* lazyOuterFunction); + ~ParserBase(); + + const char* getFilename() const { return tokenStream.getFilename(); } + JSVersion versionNumber() const { return tokenStream.versionNumber(); } + TokenPos pos() const { return tokenStream.currentToken().pos; } + + // Determine whether |yield| is a valid name in the current context, or + // whether it's prohibited due to strictness, JS version, or occurrence + // inside a star generator. + bool yieldExpressionsSupported() { + return (versionNumber() >= JSVERSION_1_7 || pc->isGenerator()) && !pc->isAsync(); + } + + 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(); + } + + bool hadAbortedSyntaxParse() { + return abortedSyntaxParse; + } + void clearAbortedSyntaxParse() { + abortedSyntaxParse = false; + } + + bool isUnexpectedEOF() const { return isUnexpectedEOF_; } + + bool reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumber, ...); + + /* Report the given error at the current offset. */ + void error(unsigned errorNumber, ...); + void errorWithNotes(UniquePtr<JSErrorNotes> notes, unsigned errorNumber, ...); + + /* Report the given error at the given offset. */ + void errorAt(uint32_t offset, unsigned errorNumber, ...); + void errorWithNotesAt(UniquePtr<JSErrorNotes> notes, uint32_t offset, + unsigned errorNumber, ...); + + /* + * Handle a strict mode error at the current offset. Report an error if in + * strict mode code, or warn if not, using the given error number and + * arguments. + */ + MOZ_MUST_USE bool strictModeError(unsigned errorNumber, ...); + + /* + * Handle a strict mode error at the given offset. Report an error if in + * strict mode code, or warn if not, using the given error number and + * arguments. + */ + MOZ_MUST_USE bool strictModeErrorAt(uint32_t offset, unsigned errorNumber, ...); + + /* Report the given warning at the current offset. */ + MOZ_MUST_USE bool warning(unsigned errorNumber, ...); + + /* Report the given warning at the given offset. */ + MOZ_MUST_USE bool warningAt(uint32_t offset, unsigned errorNumber, ...); + + /* + * If extra warnings are enabled, report the given warning at the current + * offset. + */ + MOZ_MUST_USE bool extraWarning(unsigned errorNumber, ...); + + /* + * If extra warnings are enabled, report the given warning at the given + * offset. + */ + MOZ_MUST_USE bool extraWarningAt(uint32_t offset, unsigned errorNumber, ...); + + bool isValidStrictBinding(PropertyName* name); + + bool warnOnceAboutExprClosure(); + bool warnOnceAboutForEach(); + + protected: + enum InvokedPrediction { PredictUninvoked = false, PredictInvoked = true }; + enum ForInitLocation { InForInit, NotInForInit }; +}; + +template <typename ParseHandler> +class Parser final : public ParserBase, private JS::AutoGCRooter { private: using Node = typename ParseHandler::Node; @@ -788,7 +947,7 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter class MOZ_STACK_CLASS PossibleError { private: - enum class ErrorKind { Expression, Destructuring }; + enum class ErrorKind { Expression, Destructuring, DestructuringWarning }; enum class ErrorState { None, Pending }; @@ -803,11 +962,12 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter Parser<ParseHandler>& parser_; Error exprError_; Error destructuringError_; + Error destructuringWarning_; // Returns the error report. Error& error(ErrorKind kind); - // Return true if an error is pending without reporting + // Return true if an error is pending without reporting. bool hasError(ErrorKind kind); // Resolve any pending error. @@ -819,7 +979,11 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter // If there is a pending error, report it and return false, otherwise // return true. - bool checkForError(ErrorKind kind); + MOZ_MUST_USE bool checkForError(ErrorKind kind); + + // If there is a pending warning, report it and return either false or + // true depending on the werror option, otherwise return true. + MOZ_MUST_USE bool checkForWarning(ErrorKind kind); // Transfer an existing error to another instance. void transferErrorTo(ErrorKind kind, PossibleError* other); @@ -827,23 +991,33 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter public: explicit PossibleError(Parser<ParseHandler>& parser); + // Return true if a pending destructuring error is present. + bool hasPendingDestructuringError(); + // Set a pending destructuring error. Only a single error may be set // per instance, i.e. subsequent calls to this method are ignored and // won't overwrite the existing pending error. void setPendingDestructuringErrorAt(const TokenPos& pos, unsigned errorNumber); + // Set a pending destructuring warning. Only a single warning may be + // set per instance, i.e. subsequent calls to this method are ignored + // and won't overwrite the existing pending warning. + void setPendingDestructuringWarningAt(const TokenPos& pos, unsigned errorNumber); + // Set a pending expression error. Only a single error may be set per // instance, i.e. subsequent calls to this method are ignored and won't // overwrite the existing pending error. void setPendingExpressionErrorAt(const TokenPos& pos, unsigned errorNumber); - // If there is a pending destructuring error, report it and return - // false, otherwise return true. Clears any pending expression error. - bool checkForDestructuringError(); + // If there is a pending destructuring error or warning, report it and + // return false, otherwise return true. Clears any pending expression + // error. + MOZ_MUST_USE bool checkForDestructuringErrorOrWarning(); // If there is a pending expression error, report it and return false, - // otherwise return true. Clears any pending destructuring error. - bool checkForExpressionError(); + // otherwise return true. Clears any pending destructuring error or + // warning. + MOZ_MUST_USE bool checkForExpressionError(); // Pass pending errors between possible error instances. This is useful // for extending the lifetime of a pending error beyond the scope of @@ -853,50 +1027,6 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter }; public: - ExclusiveContext* const context; - - LifoAlloc& alloc; - - TokenStream tokenStream; - LifoAlloc::Mark tempPoolMark; - - /* list of parsed objects for GC tracing */ - ObjectBox* traceListHead; - - /* innermost parse context (stack-allocated) */ - ParseContext* pc; - - // For tracking used names in this parsing session. - UsedNameTracker& usedNames; - - /* Compression token for aborting. */ - SourceCompressionTask* sct; - - ScriptSource* ss; - - /* Root atoms and objects allocated for the parsed tree. */ - AutoKeepAtoms keepAtoms; - - /* Perform constant-folding; must be true when interfacing with the emitter. */ - const bool foldConstants:1; - - private: -#if DEBUG - /* Our fallible 'checkOptions' member function has been called. */ - bool checkOptionsCalled:1; -#endif - - /* - * Not all language constructs can be handled during syntax parsing. If it - * is not known whether the parse succeeds or fails, this bit is set and - * the parse will return false. - */ - bool abortedSyntaxParse:1; - - /* Unexpected end of input, i.e. TOK_EOF not at top-level. */ - bool isUnexpectedEOF_:1; - - public: /* State specific to the kind of parse being performed. */ ParseHandler handler; @@ -904,45 +1034,14 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter void freeTree(Node node) { handler.freeTree(node); } public: - bool reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumber, ...); - - /* Report the given error at the current offset. */ - void error(unsigned errorNumber, ...); - - /* Report the given error at the given offset. */ - void errorAt(uint32_t offset, unsigned errorNumber, ...); - - /* - * Handle a strict mode error at the current offset. Report an error if in - * strict mode code, or warn if not, using the given error number and - * arguments. - */ - MOZ_MUST_USE bool strictModeError(unsigned errorNumber, ...); - - /* - * Handle a strict mode error at the given offset. Report an error if in - * strict mode code, or warn if not, using the given error number and - * arguments. - */ - MOZ_MUST_USE bool strictModeErrorAt(uint32_t offset, unsigned errorNumber, ...); - - /* Report the given warning at the current offset. */ - MOZ_MUST_USE bool warning(unsigned errorNumber, ...); - - /* Report the given warning at the given offset. */ - MOZ_MUST_USE bool warningAt(uint32_t offset, unsigned errorNumber, ...); - - /* - * If extra warnings are enabled, report the given warning at the current - * offset. - */ - MOZ_MUST_USE bool extraWarning(unsigned errorNumber, ...); - Parser(ExclusiveContext* cx, LifoAlloc& alloc, const ReadOnlyCompileOptions& options, const char16_t* chars, size_t length, bool foldConstants, UsedNameTracker& usedNames, Parser<SyntaxParseHandler>* syntaxParser, LazyScript* lazyOuterFunction); ~Parser(); + friend class AutoAwaitIsKeyword<ParseHandler>; + void setAwaitIsKeyword(bool isKeyword); + bool checkOptions(); // A Parser::Mark is the extension of the LifoAlloc::Mark to the entire @@ -967,9 +1066,6 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter friend void js::frontend::MarkParser(JSTracer* trc, JS::AutoGCRooter* parser); - const char* getFilename() const { return tokenStream.getFilename(); } - JSVersion versionNumber() const { return tokenStream.versionNumber(); } - /* * Parse a top-level JS script. */ @@ -980,7 +1076,7 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter * cx->tempLifoAlloc. */ ObjectBox* newObjectBox(JSObject* obj); - FunctionBox* newFunctionBox(Node fn, JSFunction* fun, uint32_t preludeStart, + FunctionBox* newFunctionBox(Node fn, JSFunction* fun, uint32_t toStringStart, Directives directives, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB); @@ -995,24 +1091,14 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter void trace(JSTracer* trc); - bool hadAbortedSyntaxParse() { - return abortedSyntaxParse; - } - void clearAbortedSyntaxParse() { - abortedSyntaxParse = false; - } - - bool isUnexpectedEOF() const { return isUnexpectedEOF_; } - - bool checkUnescapedName(); - private: Parser* thisForCtor() { return this; } JSAtom* stopStringCompression(); Node stringLiteral(); - Node noSubstitutionTemplate(); + Node noSubstitutionTaggedTemplate(); + Node noSubstitutionUntaggedTemplate(); Node templateLiteral(YieldHandling yieldHandling); bool taggedTemplate(YieldHandling yieldHandling, Node nodeList, TokenKind tt); bool appendToCallSiteObj(Node callSiteObj); @@ -1061,7 +1147,7 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter // Parse an inner function given an enclosing ParseContext and a // FunctionBox for the inner function. - bool innerFunction(Node pn, ParseContext* outerpc, FunctionBox* funbox, uint32_t preludeStart, + bool innerFunction(Node pn, ParseContext* outerpc, FunctionBox* funbox, uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind, Directives inheritedDirectives, Directives* newDirectives); @@ -1070,35 +1156,14 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter // ParseContext is already on the stack. bool functionFormalParametersAndBody(InHandling inHandling, YieldHandling yieldHandling, Node pn, FunctionSyntaxKind kind, - mozilla::Maybe<uint32_t> parameterListEnd = mozilla::Nothing()); - - - // Determine whether |yield| is a valid name in the current context, or - // whether it's prohibited due to strictness, JS version, or occurrence - // inside a star generator. - bool yieldExpressionsSupported() { - return (versionNumber() >= JSVERSION_1_7 || pc->isGenerator()) && !pc->isAsync(); - } + mozilla::Maybe<uint32_t> parameterListEnd = mozilla::Nothing(), + bool isStandaloneFunction = false); // Match the current token against the BindingIdentifier production with // the given Yield parameter. If there is no match, report a syntax // error. PropertyName* bindingIdentifier(YieldHandling yieldHandling); - 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(); - } - - private: - enum InvokedPrediction { PredictUninvoked = false, PredictInvoked = true }; - enum ForInitLocation { InForInit, NotInForInit }; - private: /* * JS parsers, from lowest to highest precedence. @@ -1116,10 +1181,10 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter * Some parsers have two versions: an always-inlined version (with an 'i' * suffix) and a never-inlined version (with an 'n' suffix). */ - Node functionStmt(uint32_t preludeStart, + Node functionStmt(uint32_t toStringStart, YieldHandling yieldHandling, DefaultHandling defaultHandling, FunctionAsyncKind asyncKind = SyncFunction); - Node functionExpr(uint32_t preludeStart, InvokedPrediction invoked = PredictUninvoked, + Node functionExpr(uint32_t toStringStart, InvokedPrediction invoked = PredictUninvoked, FunctionAsyncKind asyncKind = SyncFunction); Node statementList(YieldHandling yieldHandling); @@ -1160,9 +1225,27 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter // continues a LexicalDeclaration. bool nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling); - Node lexicalDeclaration(YieldHandling yieldHandling, bool isConst); + Node lexicalDeclaration(YieldHandling yieldHandling, DeclarationKind kind); Node importDeclaration(); + + bool processExport(Node node); + bool processExportFrom(Node node); + + Node exportFrom(uint32_t begin, Node specList); + Node exportBatch(uint32_t begin); + bool checkLocalExportNames(Node node); + Node exportClause(uint32_t begin); + Node exportFunctionDeclaration(uint32_t begin); + Node exportVariableStatement(uint32_t begin); + Node exportClassDeclaration(uint32_t begin); + Node exportLexicalDeclaration(uint32_t begin, DeclarationKind kind); + Node exportDefaultFunctionDeclaration(uint32_t begin, + FunctionAsyncKind asyncKind = SyncFunction); + Node exportDefaultClassDeclaration(uint32_t begin); + Node exportDefaultAssignExpr(uint32_t begin); + Node exportDefault(uint32_t begin); + Node exportDeclaration(); Node expressionStatement(YieldHandling yieldHandling, InvokedPrediction invoked = PredictUninvoked); @@ -1250,7 +1333,7 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter bool tryNewTarget(Node& newTarget); bool checkAndMarkSuperScope(); - Node methodDefinition(uint32_t preludeStart, PropertyType propType, HandleAtom funName); + Node methodDefinition(uint32_t toStringStart, PropertyType propType, HandleAtom funName); /* * Additional JS parsers. @@ -1258,7 +1341,7 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter bool functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind, Node funcpn); - Node functionDefinition(uint32_t preludeStart, Node pn, + Node functionDefinition(uint32_t toStringStart, Node pn, InHandling inHandling, YieldHandling yieldHandling, HandleAtom name, FunctionSyntaxKind kind, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, @@ -1294,21 +1377,34 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter bool checkExportedName(JSAtom* exportName); bool checkExportedNamesForDeclaration(Node node); + bool checkExportedNameForClause(Node node); + bool checkExportedNameForFunction(Node node); + bool checkExportedNameForClass(Node node); + enum ClassContext { ClassStatement, ClassExpression }; Node classDefinition(YieldHandling yieldHandling, ClassContext classContext, DefaultHandling defaultHandling); - PropertyName* labelOrIdentifierReference(YieldHandling yieldHandling, - bool yieldTokenizedAsName); + bool checkLabelOrIdentifierReference(HandlePropertyName ident, + uint32_t offset, + YieldHandling yieldHandling); + + bool checkLocalExportName(HandlePropertyName ident, uint32_t offset) { + return checkLabelOrIdentifierReference(ident, offset, YieldIsName); + } + + bool checkBindingIdentifier(HandlePropertyName ident, + uint32_t offset, + YieldHandling yieldHandling); + + PropertyName* labelOrIdentifierReference(YieldHandling yieldHandling); PropertyName* labelIdentifier(YieldHandling yieldHandling) { - return labelOrIdentifierReference(yieldHandling, false); + return labelOrIdentifierReference(yieldHandling); } - PropertyName* identifierReference(YieldHandling yieldHandling, - bool yieldTokenizedAsName = false) - { - return labelOrIdentifierReference(yieldHandling, yieldTokenizedAsName); + PropertyName* identifierReference(YieldHandling yieldHandling) { + return labelOrIdentifierReference(yieldHandling); } PropertyName* importedBinding() { @@ -1337,23 +1433,27 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter Node newDotGeneratorName(); bool declareDotGeneratorName(); - bool skipLazyInnerFunction(Node pn, uint32_t preludeStart, FunctionSyntaxKind kind, + bool skipLazyInnerFunction(Node pn, uint32_t toStringStart, FunctionSyntaxKind kind, bool tryAnnexB); - bool innerFunction(Node pn, ParseContext* outerpc, HandleFunction fun, uint32_t preludeStart, + bool innerFunction(Node pn, ParseContext* outerpc, HandleFunction fun, uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB, Directives inheritedDirectives, Directives* newDirectives); - bool trySyntaxParseInnerFunction(Node pn, HandleFunction fun, uint32_t preludeStart, + bool trySyntaxParseInnerFunction(Node pn, HandleFunction fun, uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB, Directives inheritedDirectives, Directives* newDirectives); - bool finishFunctionScopes(); - bool finishFunction(); + bool finishFunctionScopes(bool isStandaloneFunction); + bool finishFunction(bool isStandaloneFunction = false); bool leaveInnerFunction(ParseContext* outerpc); + bool matchOrInsertSemicolonHelper(TokenStream::Modifier modifier); + bool matchOrInsertSemicolonAfterExpression(); + bool matchOrInsertSemicolonAfterNonExpression(); + public: enum FunctionCallBehavior { PermitAssignmentToFunctionCalls, @@ -1366,21 +1466,22 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter private: bool checkIncDecOperand(Node operand, uint32_t operandOffset); bool checkStrictAssignment(Node lhs); - bool checkStrictBinding(PropertyName* name, TokenPos pos); bool hasValidSimpleStrictParameterNames(); - bool isValidStrictBinding(PropertyName* name); + void reportMissingClosing(unsigned errorNumber, unsigned noteNumber, uint32_t openedPos); - void reportRedeclaration(HandlePropertyName name, DeclarationKind kind, TokenPos pos); - bool notePositionalFormalParameter(Node fn, HandlePropertyName name, + void reportRedeclaration(HandlePropertyName name, DeclarationKind prevKind, TokenPos pos, + uint32_t prevPos); + bool notePositionalFormalParameter(Node fn, HandlePropertyName name, uint32_t beginPos, bool disallowDuplicateParams, bool* duplicatedParam); bool noteDestructuredPositionalFormalParameter(Node fn, Node destruct); mozilla::Maybe<DeclarationKind> isVarRedeclaredInEval(HandlePropertyName name, DeclarationKind kind); - bool tryDeclareVar(HandlePropertyName name, DeclarationKind kind, - mozilla::Maybe<DeclarationKind>* redeclaredKind); - bool tryDeclareVarForAnnexBLexicalFunction(HandlePropertyName name, bool* tryAnnexB); + bool tryDeclareVar(HandlePropertyName name, DeclarationKind kind, uint32_t beginPos, + mozilla::Maybe<DeclarationKind>* redeclaredKind, uint32_t* prevPos); + bool tryDeclareVarForAnnexBLexicalFunction(HandlePropertyName name, uint32_t beginPos, + bool* tryAnnexB); bool checkLexicalDeclarationDirectlyWithinBlock(ParseContext::Statement& stmt, DeclarationKind kind, TokenPos pos); bool noteDeclaredName(HandlePropertyName name, DeclarationKind kind, TokenPos pos); @@ -1399,25 +1500,27 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter mozilla::Maybe<LexicalScope::Data*> newLexicalScopeData(ParseContext::Scope& scope); Node finishLexicalScope(ParseContext::Scope& scope, Node body); - Node propertyName(YieldHandling yieldHandling, Node propList, + Node propertyName(YieldHandling yieldHandling, + const mozilla::Maybe<DeclarationKind>& maybeDecl, Node propList, PropertyType* propType, MutableHandleAtom propAtom); - Node computedPropertyName(YieldHandling yieldHandling, Node literal); + Node computedPropertyName(YieldHandling yieldHandling, + const mozilla::Maybe<DeclarationKind>& maybeDecl, Node literal); Node arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError); Node newRegExp(); Node objectLiteral(YieldHandling yieldHandling, PossibleError* possibleError); - // Top-level entrypoint into destructuring pattern checking/name-analyzing. - bool checkDestructuringPattern(Node pattern, mozilla::Maybe<DeclarationKind> maybeDecl, - PossibleError* possibleError = nullptr); + Node bindingInitializer(Node lhs, DeclarationKind kind, YieldHandling yieldHandling); + Node bindingIdentifier(DeclarationKind kind, YieldHandling yieldHandling); + Node bindingIdentifierOrPattern(DeclarationKind kind, YieldHandling yieldHandling, + TokenKind tt); + Node objectBindingPattern(DeclarationKind kind, YieldHandling yieldHandling); + Node arrayBindingPattern(DeclarationKind kind, YieldHandling yieldHandling); - // Recursive methods for checking/name-analyzing subcomponents of a - // destructuring pattern. The array/object methods *must* be passed arrays - // or objects. The name method may be passed anything but will report an - // error if not passed a name. - bool checkDestructuringArray(Node arrayPattern, mozilla::Maybe<DeclarationKind> maybeDecl); - bool checkDestructuringObject(Node objectPattern, mozilla::Maybe<DeclarationKind> maybeDecl); - bool checkDestructuringName(Node expr, mozilla::Maybe<DeclarationKind> maybeDecl); + void checkDestructuringAssignmentTarget(Node expr, TokenPos exprPos, + PossibleError* possibleError); + void checkDestructuringAssignmentElement(Node expr, TokenPos exprPos, + PossibleError* possibleError); Node newNumber(const Token& tok) { return handler.newNumber(tok.number(), tok.decimalPoint(), tok.pos); @@ -1427,12 +1530,26 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter JSAtom* prefixAccessorName(PropertyType propType, HandleAtom propAtom); - TokenPos pos() const { return tokenStream.currentToken().pos; } - bool asmJS(Node list); +}; - bool warnOnceAboutExprClosure(); - bool warnOnceAboutForEach(); +template <typename ParseHandler> +class MOZ_STACK_CLASS AutoAwaitIsKeyword +{ + private: + Parser<ParseHandler>* parser_; + bool oldAwaitIsKeyword_; + + public: + AutoAwaitIsKeyword(Parser<ParseHandler>* parser, bool awaitIsKeyword) { + parser_ = parser; + oldAwaitIsKeyword_ = parser_->awaitIsKeyword_; + parser_->setAwaitIsKeyword(awaitIsKeyword); + } + + ~AutoAwaitIsKeyword() { + parser_->setAwaitIsKeyword(oldAwaitIsKeyword_); + } }; } /* namespace frontend */ diff --git a/js/src/frontend/ReservedWords.h b/js/src/frontend/ReservedWords.h new file mode 100644 index 000000000..27f5b11c1 --- /dev/null +++ b/js/src/frontend/ReservedWords.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* A higher-order macro for enumerating reserved word tokens. */ + +#ifndef vm_ReservedWords_h +#define vm_ReservedWords_h + +#define FOR_EACH_JAVASCRIPT_RESERVED_WORD(macro) \ + macro(false, false_, TOK_FALSE) \ + macro(true, true_, TOK_TRUE) \ + macro(null, null, TOK_NULL) \ + \ + /* Keywords. */ \ + macro(break, break_, TOK_BREAK) \ + macro(case, case_, TOK_CASE) \ + macro(catch, catch_, TOK_CATCH) \ + macro(const, const_, TOK_CONST) \ + macro(continue, continue_, TOK_CONTINUE) \ + macro(debugger, debugger, TOK_DEBUGGER) \ + macro(default, default_, TOK_DEFAULT) \ + macro(delete, delete_, TOK_DELETE) \ + macro(do, do_, TOK_DO) \ + macro(else, else_, TOK_ELSE) \ + macro(finally, finally_, TOK_FINALLY) \ + macro(for, for_, TOK_FOR) \ + macro(function, function, TOK_FUNCTION) \ + macro(if, if_, TOK_IF) \ + macro(in, in, TOK_IN) \ + macro(instanceof, instanceof, TOK_INSTANCEOF) \ + macro(new, new_, TOK_NEW) \ + macro(return, return_, TOK_RETURN) \ + macro(switch, switch_, TOK_SWITCH) \ + macro(this, this_, TOK_THIS) \ + macro(throw, throw_, TOK_THROW) \ + macro(try, try_, TOK_TRY) \ + macro(typeof, typeof_, TOK_TYPEOF) \ + macro(var, var, TOK_VAR) \ + macro(void, void_, TOK_VOID) \ + macro(while, while_, TOK_WHILE) \ + macro(with, with, TOK_WITH) \ + macro(import, import, TOK_IMPORT) \ + macro(export, export_, TOK_EXPORT) \ + macro(class, class_, TOK_CLASS) \ + macro(extends, extends, TOK_EXTENDS) \ + macro(super, super, TOK_SUPER) \ + \ + /* Future reserved words. */ \ + macro(enum, enum_, TOK_ENUM) \ + \ + /* Future reserved words, but only in strict mode. */ \ + macro(implements, implements, TOK_IMPLEMENTS) \ + macro(interface, interface, TOK_INTERFACE) \ + macro(package, package, TOK_PACKAGE) \ + macro(private, private_, TOK_PRIVATE) \ + macro(protected, protected_, TOK_PROTECTED) \ + macro(public, public_, TOK_PUBLIC) \ + \ + /* Contextual keywords. */ \ + macro(as, as, TOK_AS) \ + macro(async, async, TOK_ASYNC) \ + macro(await, await, TOK_AWAIT) \ + macro(each, each, TOK_EACH) \ + macro(from, from, TOK_FROM) \ + macro(get, get, TOK_GET) \ + macro(let, let, TOK_LET) \ + macro(of, of, TOK_OF) \ + macro(set, set, TOK_SET) \ + macro(static, static_, TOK_STATIC) \ + macro(target, target, TOK_TARGET) \ + /* \ + * Yield is a token inside function*. Outside of a function*, it is a \ + * future reserved word in strict mode, but a keyword in JS1.7 even \ + * when strict. Punt logic to parser. \ + */ \ + macro(yield, yield, TOK_YIELD) + +#endif /* vm_ReservedWords_h */ diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index b20417d5d..013444690 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -38,6 +38,7 @@ enum class StatementKind : uint8_t ForOfLoop, DoLoop, WhileLoop, + Class, // Used only by BytecodeEmitter. Spread @@ -450,7 +451,8 @@ class FunctionBox : public ObjectBox, public SharedContext uint32_t bufEnd; uint32_t startLine; uint32_t startColumn; - uint32_t preludeStart; + uint32_t toStringStart; + uint32_t toStringEnd; uint16_t length; uint8_t generatorKindBits_; /* The GeneratorKind of this function. */ @@ -473,11 +475,14 @@ class FunctionBox : public ObjectBox, public SharedContext bool usesThis:1; /* contains 'this' */ bool usesReturn:1; /* contains a 'return' statement */ bool hasRest_:1; /* has rest parameter */ + bool isExprBody_:1; /* arrow function with expression + * body or expression closure: + * function(x) x*x */ FunctionContextFlags funCxFlags; FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead, JSFunction* fun, - uint32_t preludeStart, Directives directives, bool extraWarnings, + uint32_t toStringStart, Directives directives, bool extraWarnings, GeneratorKind generatorKind, FunctionAsyncKind asyncKind); MutableHandle<LexicalScope::Data*> namedLambdaBindings() { @@ -546,6 +551,11 @@ class FunctionBox : public ObjectBox, public SharedContext hasRest_ = true; } + bool isExprBody() const { return isExprBody_; } + void setIsExprBody() { + isExprBody_ = true; + } + void setGeneratorKind(GeneratorKind kind) { // A generator kind can be set at initialization, or when "yield" is // first seen. In both cases the transition can only happen from @@ -595,6 +605,14 @@ class FunctionBox : public ObjectBox, public SharedContext tokenStream.srcCoords.lineNumAndColumnIndex(bufStart, &startLine, &startColumn); } + void setEnd(uint32_t end) { + // For all functions except class constructors, the buffer and + // toString ending positions are the same. Class constructors override + // the toString ending position with the end of the class definition. + bufEnd = end; + toStringEnd = end; + } + void trace(JSTracer* trc) override; }; diff --git a/js/src/frontend/SourceNotes.h b/js/src/frontend/SourceNotes.h index dd2a95ad1..6ae184ae4 100644 --- a/js/src/frontend/SourceNotes.h +++ b/js/src/frontend/SourceNotes.h @@ -56,13 +56,14 @@ namespace js { M(SRC_NEXTCASE, "nextcase", 1) /* Distance forward from one CASE in a CONDSWITCH to \ the next. */ \ M(SRC_ASSIGNOP, "assignop", 0) /* += or another assign-op follows. */ \ + M(SRC_CLASS_SPAN, "class", 2) /* The starting and ending offsets for the class, used \ + for toString correctness for default ctors. */ \ M(SRC_TRY, "try", 1) /* JSOP_TRY, offset points to goto at the end of the \ try block. */ \ /* All notes above here are "gettable". See SN_IS_GETTABLE below. */ \ M(SRC_COLSPAN, "colspan", 1) /* Number of columns this opcode spans. */ \ M(SRC_NEWLINE, "newline", 0) /* Bytecode follows a source newline. */ \ M(SRC_SETLINE, "setline", 1) /* A file-absolute source line number note. */ \ - M(SRC_UNUSED20, "unused20", 0) /* Unused. */ \ M(SRC_UNUSED21, "unused21", 0) /* Unused. */ \ M(SRC_UNUSED22, "unused22", 0) /* Unused. */ \ M(SRC_UNUSED23, "unused23", 0) /* Unused. */ \ diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 00ea9d35d..a604b599f 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -9,6 +9,8 @@ #include "mozilla/Attributes.h" +#include <string.h> + #include "frontend/ParseNode.h" #include "frontend/TokenStream.h" @@ -94,10 +96,13 @@ class SyntaxParseHandler // Nodes representing unparenthesized names. NodeUnparenthesizedArgumentsName, - NodeUnparenthesizedAsyncName, NodeUnparenthesizedEvalName, NodeUnparenthesizedName, + // Node representing the "async" name, which may actually be a + // contextual keyword. + NodePotentialAsyncKeyword, + // Valuable for recognizing potential destructuring patterns. NodeUnparenthesizedArray, NodeUnparenthesizedObject, @@ -183,8 +188,8 @@ class SyntaxParseHandler lastAtom = name; if (name == cx->names().arguments) return NodeUnparenthesizedArgumentsName; - if (name == cx->names().async) - return NodeUnparenthesizedAsyncName; + if (pos.begin + strlen("async") == pos.end && name == cx->names().async) + return NodePotentialAsyncKeyword; if (name == cx->names().eval) return NodeUnparenthesizedEvalName; return NodeUnparenthesizedName; @@ -219,6 +224,7 @@ class SyntaxParseHandler Node newThisLiteral(const TokenPos& pos, Node thisName) { return NodeGeneric; } Node newNullLiteral(const TokenPos& pos) { return NodeGeneric; } + Node newRawUndefinedLiteral(const TokenPos& pos) { return NodeGeneric; } template <class Boxer> Node newRegExp(RegExpObject* reobj, const TokenPos& pos, Boxer& boxer) { return NodeGeneric; } @@ -235,6 +241,10 @@ class SyntaxParseHandler return NodeUnparenthesizedUnary; } + Node newNullary(ParseNodeKind kind, JSOp op, const TokenPos& pos) { + return NodeGeneric; + } + Node newUnary(ParseNodeKind kind, JSOp op, uint32_t begin, Node kid) { return NodeUnparenthesizedUnary; } @@ -279,7 +289,7 @@ class SyntaxParseHandler Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; } Node newClassMethodList(uint32_t begin) { return NodeGeneric; } Node newClassNames(Node outer, Node inner, const TokenPos& pos) { return NodeGeneric; } - Node newClass(Node name, Node heritage, Node methodBlock) { return NodeGeneric; } + Node newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) { return NodeGeneric; } Node newNewTarget(Node newHolder, Node targetHolder) { return NodeGeneric; } Node newPosHolder(const TokenPos& pos) { return NodeGeneric; } @@ -288,6 +298,7 @@ class SyntaxParseHandler MOZ_MUST_USE bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; } MOZ_MUST_USE bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; } MOZ_MUST_USE bool addShorthand(Node literal, Node name, Node expr) { return true; } + MOZ_MUST_USE bool addSpreadProperty(Node literal, uint32_t begin, Node inner) { return true; } MOZ_MUST_USE bool addObjectMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; } MOZ_MUST_USE bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op, bool isStatic) { return true; } Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; } @@ -302,6 +313,16 @@ class SyntaxParseHandler MOZ_MUST_USE bool prependInitialYield(Node stmtList, Node gen) { return true; } Node newEmptyStatement(const TokenPos& pos) { return NodeEmptyStatement; } + Node newExportDeclaration(Node kid, const TokenPos& pos) { + return NodeGeneric; + } + Node newExportFromDeclaration(uint32_t begin, Node exportSpecSet, Node moduleSpec) { + return NodeGeneric; + } + Node newExportDefaultDeclaration(Node kid, Node maybeBinding, const TokenPos& pos) { + return NodeGeneric; + } + Node newSetThis(Node thisName, Node value) { return value; } Node newExprStatement(Node expr, uint32_t end) { @@ -497,7 +518,7 @@ class SyntaxParseHandler return NodeParenthesizedArgumentsName; if (node == NodeUnparenthesizedEvalName) return NodeParenthesizedEvalName; - if (node == NodeUnparenthesizedName || node == NodeUnparenthesizedAsyncName) + if (node == NodeUnparenthesizedName || node == NodePotentialAsyncKeyword) return NodeParenthesizedName; if (node == NodeUnparenthesizedArray) @@ -528,9 +549,9 @@ class SyntaxParseHandler bool isUnparenthesizedName(Node node) { return node == NodeUnparenthesizedArgumentsName || - node == NodeUnparenthesizedAsyncName || node == NodeUnparenthesizedEvalName || - node == NodeUnparenthesizedName; + node == NodeUnparenthesizedName || + node == NodePotentialAsyncKeyword; } bool isNameAnyParentheses(Node node) { @@ -541,9 +562,11 @@ class SyntaxParseHandler node == NodeParenthesizedName; } - bool nameIsEvalAnyParentheses(Node node, ExclusiveContext* cx) { - MOZ_ASSERT(isNameAnyParentheses(node), - "must only call this function on known names"); + bool isArgumentsAnyParentheses(Node node, ExclusiveContext* cx) { + return node == NodeUnparenthesizedArgumentsName || node == NodeParenthesizedArgumentsName; + } + + bool isEvalAnyParentheses(Node node, ExclusiveContext* cx) { return node == NodeUnparenthesizedEvalName || node == NodeParenthesizedEvalName; } @@ -551,17 +574,15 @@ class SyntaxParseHandler MOZ_ASSERT(isNameAnyParentheses(node), "must only call this method on known names"); - if (nameIsEvalAnyParentheses(node, cx)) + if (isEvalAnyParentheses(node, cx)) return js_eval_str; - if (node == NodeUnparenthesizedArgumentsName || node == NodeParenthesizedArgumentsName) + if (isArgumentsAnyParentheses(node, cx)) return js_arguments_str; return nullptr; } - bool nameIsUnparenthesizedAsync(Node node, ExclusiveContext* cx) { - MOZ_ASSERT(isNameAnyParentheses(node), - "must only call this function on known names"); - return node == NodeUnparenthesizedAsyncName; + bool isAsyncKeyword(Node node, ExclusiveContext* cx) { + return node == NodePotentialAsyncKeyword; } PropertyName* maybeDottedProperty(Node node) { diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index 6f22d78e5..98f23fec8 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -81,9 +81,12 @@ \ macro(REGEXP, "regular expression literal") \ macro(TRUE, "boolean literal 'true'") \ + range(RESERVED_WORD_LITERAL_FIRST, TRUE) \ macro(FALSE, "boolean literal 'false'") \ macro(NULL, "null literal") \ + range(RESERVED_WORD_LITERAL_LAST, NULL) \ macro(THIS, "keyword 'this'") \ + range(KEYWORD_FIRST, THIS) \ macro(FUNCTION, "keyword 'function'") \ macro(IF, "keyword 'if'") \ macro(ELSE, "keyword 'else'") \ @@ -106,16 +109,43 @@ macro(FINALLY, "keyword 'finally'") \ macro(THROW, "keyword 'throw'") \ macro(DEBUGGER, "keyword 'debugger'") \ - macro(YIELD, "keyword 'yield'") \ - macro(AWAIT, "keyword 'await'") \ macro(EXPORT, "keyword 'export'") \ macro(IMPORT, "keyword 'import'") \ macro(CLASS, "keyword 'class'") \ macro(EXTENDS, "keyword 'extends'") \ macro(SUPER, "keyword 'super'") \ - macro(RESERVED, "reserved keyword") \ - /* reserved keywords in strict mode */ \ - macro(STRICT_RESERVED, "reserved keyword") \ + range(KEYWORD_LAST, SUPER) \ + \ + /* contextual keywords */ \ + macro(AS, "'as'") \ + range(CONTEXTUAL_KEYWORD_FIRST, AS) \ + macro(ASYNC, "'async'") \ + macro(AWAIT, "'await'") \ + macro(EACH, "'each'") \ + macro(FROM, "'from'") \ + macro(GET, "'get'") \ + macro(LET, "'let'") \ + macro(OF, "'of'") \ + macro(SET, "'set'") \ + macro(STATIC, "'static'") \ + macro(TARGET, "'target'") \ + macro(YIELD, "'yield'") \ + range(CONTEXTUAL_KEYWORD_LAST, YIELD) \ + \ + /* future reserved words */ \ + macro(ENUM, "reserved word 'enum'") \ + range(FUTURE_RESERVED_KEYWORD_FIRST, ENUM) \ + range(FUTURE_RESERVED_KEYWORD_LAST, ENUM) \ + \ + /* reserved words in strict mode */ \ + macro(IMPLEMENTS, "reserved word 'implements'") \ + range(STRICT_RESERVED_KEYWORD_FIRST, IMPLEMENTS) \ + macro(INTERFACE, "reserved word 'interface'") \ + macro(PACKAGE, "reserved word 'package'") \ + macro(PRIVATE, "reserved word 'private'") \ + macro(PROTECTED, "reserved word 'protected'") \ + macro(PUBLIC, "reserved word 'public'") \ + range(STRICT_RESERVED_KEYWORD_LAST, PUBLIC) \ \ /* \ * The following token types occupy contiguous ranges to enable easy \ @@ -149,7 +179,9 @@ range(RELOP_LAST, GE) \ \ macro(INSTANCEOF, "keyword 'instanceof'") \ + range(KEYWORD_BINOP_FIRST, INSTANCEOF) \ macro(IN, "keyword 'in'") \ + range(KEYWORD_BINOP_LAST, IN) \ \ /* Shift ops, per TokenKindIsShift. */ \ macro(LSH, "'<<'") \ @@ -168,7 +200,9 @@ \ /* Unary operation tokens. */ \ macro(TYPEOF, "keyword 'typeof'") \ + range(KEYWORD_UNOP_FIRST, TYPEOF) \ macro(VOID, "keyword 'void'") \ + range(KEYWORD_UNOP_LAST, VOID) \ macro(NOT, "'!'") \ macro(BITNOT, "'~'") \ \ @@ -239,6 +273,61 @@ TokenKindIsAssignment(TokenKind tt) return TOK_ASSIGNMENT_START <= tt && tt <= TOK_ASSIGNMENT_LAST; } +inline MOZ_MUST_USE bool +TokenKindIsKeyword(TokenKind tt) +{ + return (TOK_KEYWORD_FIRST <= tt && tt <= TOK_KEYWORD_LAST) || + (TOK_KEYWORD_BINOP_FIRST <= tt && tt <= TOK_KEYWORD_BINOP_LAST) || + (TOK_KEYWORD_UNOP_FIRST <= tt && tt <= TOK_KEYWORD_UNOP_LAST); +} + +inline MOZ_MUST_USE bool +TokenKindIsContextualKeyword(TokenKind tt) +{ + return TOK_CONTEXTUAL_KEYWORD_FIRST <= tt && tt <= TOK_CONTEXTUAL_KEYWORD_LAST; +} + +inline MOZ_MUST_USE bool +TokenKindIsFutureReservedWord(TokenKind tt) +{ + return TOK_FUTURE_RESERVED_KEYWORD_FIRST <= tt && tt <= TOK_FUTURE_RESERVED_KEYWORD_LAST; +} + +inline MOZ_MUST_USE bool +TokenKindIsStrictReservedWord(TokenKind tt) +{ + return TOK_STRICT_RESERVED_KEYWORD_FIRST <= tt && tt <= TOK_STRICT_RESERVED_KEYWORD_LAST; +} + +inline MOZ_MUST_USE bool +TokenKindIsReservedWordLiteral(TokenKind tt) +{ + return TOK_RESERVED_WORD_LITERAL_FIRST <= tt && tt <= TOK_RESERVED_WORD_LITERAL_LAST; +} + +inline MOZ_MUST_USE bool +TokenKindIsReservedWord(TokenKind tt) +{ + return TokenKindIsKeyword(tt) || + TokenKindIsFutureReservedWord(tt) || + TokenKindIsReservedWordLiteral(tt); +} + +inline MOZ_MUST_USE bool +TokenKindIsPossibleIdentifier(TokenKind tt) +{ + return tt == TOK_NAME || + TokenKindIsContextualKeyword(tt) || + TokenKindIsStrictReservedWord(tt); +} + +inline MOZ_MUST_USE bool +TokenKindIsPossibleIdentifierName(TokenKind tt) +{ + return TokenKindIsPossibleIdentifier(tt) || + TokenKindIsReservedWord(tt); +} + } // namespace frontend } // namespace js diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 8438ff7c5..b8623d545 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -8,6 +8,7 @@ #include "frontend/TokenStream.h" +#include "mozilla/ArrayUtils.h" #include "mozilla/IntegerTypeTraits.h" #include "mozilla/PodOperations.h" @@ -23,80 +24,81 @@ #include "jsnum.h" #include "frontend/BytecodeCompiler.h" +#include "frontend/ReservedWords.h" #include "js/CharacterEncoding.h" #include "js/UniquePtr.h" #include "vm/HelperThreads.h" -#include "vm/Keywords.h" #include "vm/StringBuffer.h" #include "vm/Unicode.h" using namespace js; using namespace js::frontend; +using mozilla::ArrayLength; using mozilla::Maybe; using mozilla::PodAssign; using mozilla::PodCopy; using mozilla::PodZero; -struct KeywordInfo { - const char* chars; // C string with keyword text +struct ReservedWordInfo { + const char* chars; // C string with reserved word text TokenKind tokentype; }; -static const KeywordInfo keywords[] = { -#define KEYWORD_INFO(keyword, name, type) \ - {js_##keyword##_str, type}, - FOR_EACH_JAVASCRIPT_KEYWORD(KEYWORD_INFO) -#undef KEYWORD_INFO +static const ReservedWordInfo reservedWords[] = { +#define RESERVED_WORD_INFO(word, name, type) \ + {js_##word##_str, type}, + FOR_EACH_JAVASCRIPT_RESERVED_WORD(RESERVED_WORD_INFO) +#undef RESERVED_WORD_INFO }; -// Returns a KeywordInfo for the specified characters, or nullptr if the string -// is not a keyword. +// Returns a ReservedWordInfo for the specified characters, or nullptr if the +// string is not a reserved word. template <typename CharT> -static const KeywordInfo* -FindKeyword(const CharT* s, size_t length) +static const ReservedWordInfo* +FindReservedWord(const CharT* s, size_t length) { MOZ_ASSERT(length != 0); size_t i; - const KeywordInfo* kw; + const ReservedWordInfo* rw; const char* chars; -#define JSKW_LENGTH() length -#define JSKW_AT(column) s[column] -#define JSKW_GOT_MATCH(index) i = (index); goto got_match; -#define JSKW_TEST_GUESS(index) i = (index); goto test_guess; -#define JSKW_NO_MATCH() goto no_match; -#include "jsautokw.h" -#undef JSKW_NO_MATCH -#undef JSKW_TEST_GUESS -#undef JSKW_GOT_MATCH -#undef JSKW_AT -#undef JSKW_LENGTH +#define JSRW_LENGTH() length +#define JSRW_AT(column) s[column] +#define JSRW_GOT_MATCH(index) i = (index); goto got_match; +#define JSRW_TEST_GUESS(index) i = (index); goto test_guess; +#define JSRW_NO_MATCH() goto no_match; +#include "frontend/ReservedWordsGenerated.h" +#undef JSRW_NO_MATCH +#undef JSRW_TEST_GUESS +#undef JSRW_GOT_MATCH +#undef JSRW_AT +#undef JSRW_LENGTH got_match: - return &keywords[i]; + return &reservedWords[i]; test_guess: - kw = &keywords[i]; - chars = kw->chars; + rw = &reservedWords[i]; + chars = rw->chars; do { if (*s++ != (unsigned char)(*chars++)) goto no_match; } while (--length != 0); - return kw; + return rw; no_match: return nullptr; } -static const KeywordInfo* -FindKeyword(JSLinearString* str) +static const ReservedWordInfo* +FindReservedWord(JSLinearString* str) { JS::AutoCheckCannotGC nogc; return str->hasLatin1Chars() - ? FindKeyword(str->latin1Chars(nogc), str->length()) - : FindKeyword(str->twoByteChars(nogc), str->length()); + ? FindReservedWord(str->latin1Chars(nogc), str->length()) + : FindReservedWord(str->twoByteChars(nogc), str->length()); } template <typename CharT> @@ -186,7 +188,68 @@ frontend::IsIdentifier(const char16_t* chars, size_t length) bool frontend::IsKeyword(JSLinearString* str) { - return FindKeyword(str) != nullptr; + if (const ReservedWordInfo* rw = FindReservedWord(str)) + return TokenKindIsKeyword(rw->tokentype); + + return false; +} + +bool +frontend::IsFutureReservedWord(JSLinearString* str) +{ + if (const ReservedWordInfo* rw = FindReservedWord(str)) + return TokenKindIsFutureReservedWord(rw->tokentype); + + return false; +} + +bool +frontend::IsStrictReservedWord(JSLinearString* str) +{ + if (const ReservedWordInfo* rw = FindReservedWord(str)) + return TokenKindIsStrictReservedWord(rw->tokentype); + + return false; +} + +bool +frontend::IsReservedWordLiteral(JSLinearString* str) +{ + if (const ReservedWordInfo* rw = FindReservedWord(str)) + return TokenKindIsReservedWordLiteral(rw->tokentype); + + return false; +} + +const char* +frontend::ReservedWordToCharZ(PropertyName* str) +{ + const ReservedWordInfo* rw = FindReservedWord(str); + if (rw == nullptr) + return nullptr; + + switch (rw->tokentype) { +#define EMIT_CASE(word, name, type) case type: return js_##word##_str; + FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE) +#undef EMIT_CASE + default: + MOZ_ASSERT_UNREACHABLE("Not a reserved word PropertyName."); + } + return nullptr; +} + +PropertyName* +TokenStream::reservedWordToPropertyName(TokenKind tt) const +{ + MOZ_ASSERT(tt != TOK_NAME); + switch (tt) { +#define EMIT_CASE(word, name, type) case type: return cx->names().name; + FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE) +#undef EMIT_CASE + default: + MOZ_ASSERT_UNREACHABLE("Not a reserved word TokenKind."); + } + return nullptr; } TokenStream::SourceCoords::SourceCoords(ExclusiveContext* cx, uint32_t ln) @@ -223,8 +286,13 @@ TokenStream::SourceCoords::add(uint32_t lineNum, uint32_t lineStartOffset) // only if lineStartOffsets_.append succeeds, to keep sentinel. // Otherwise return false to tell TokenStream about OOM. uint32_t maxPtr = MAX_PTR; - if (!lineStartOffsets_.append(maxPtr)) + if (!lineStartOffsets_.append(maxPtr)) { + static_assert(mozilla::IsSame<decltype(lineStartOffsets_.allocPolicy()), + TempAllocPolicy&>::value, + "this function's caller depends on it reporting an " + "error on failure, as TempAllocPolicy ensures"); return false; + } lineStartOffsets_[lineIndex] = lineStartOffset; } else { @@ -554,8 +622,9 @@ TokenStream::advance(size_t position) MOZ_MAKE_MEM_UNDEFINED(&cur->type, sizeof(cur->type)); lookahead = 0; - if (flags.hitOOM) - return reportError(JSMSG_OUT_OF_MEMORY); + if (flags.hitOOM) { + return false; + } return true; } @@ -599,8 +668,8 @@ TokenStream::seek(const Position& pos, const TokenStream& other) } bool -TokenStream::reportStrictModeErrorNumberVA(uint32_t offset, bool strictMode, unsigned errorNumber, - va_list args) +TokenStream::reportStrictModeErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset, + bool strictMode, unsigned errorNumber, va_list args) { // In strict mode code, this is an error, not merely a warning. unsigned flags; @@ -611,7 +680,7 @@ TokenStream::reportStrictModeErrorNumberVA(uint32_t offset, bool strictMode, uns else return true; - return reportCompileErrorNumberVA(offset, flags, errorNumber, args); + return reportCompileErrorNumberVA(Move(notes), offset, flags, errorNumber, args); } void @@ -637,8 +706,8 @@ CompileError::throwError(JSContext* cx) } bool -TokenStream::reportCompileErrorNumberVA(uint32_t offset, unsigned flags, unsigned errorNumber, - va_list args) +TokenStream::reportCompileErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset, + unsigned flags, unsigned errorNumber, va_list args) { bool warning = JSREPORT_IS_WARNING(flags); @@ -655,6 +724,7 @@ TokenStream::reportCompileErrorNumberVA(uint32_t offset, unsigned flags, unsigne return false; CompileError& err = *tempErrPtr; + err.notes = Move(notes); err.flags = flags; err.errorNumber = errorNumber; err.filename = filename; @@ -746,7 +816,7 @@ TokenStream::reportStrictModeError(unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - bool result = reportStrictModeErrorNumberVA(currentToken().pos.begin, strictMode(), + bool result = reportStrictModeErrorNumberVA(nullptr, currentToken().pos.begin, strictMode(), errorNumber, args); va_end(args); return result; @@ -757,8 +827,8 @@ TokenStream::reportError(unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - bool result = reportCompileErrorNumberVA(currentToken().pos.begin, JSREPORT_ERROR, errorNumber, - args); + bool result = reportCompileErrorNumberVA(nullptr, currentToken().pos.begin, JSREPORT_ERROR, + errorNumber, args); va_end(args); return result; } @@ -768,30 +838,32 @@ TokenStream::reportErrorNoOffset(unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - bool result = reportCompileErrorNumberVA(NoOffset, JSREPORT_ERROR, errorNumber, - args); + bool result = reportCompileErrorNumberVA(nullptr, NoOffset, JSREPORT_ERROR, + errorNumber, args); va_end(args); return result; } bool -TokenStream::reportWarning(unsigned errorNumber, ...) +TokenStream::warning(unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - bool result = reportCompileErrorNumberVA(currentToken().pos.begin, JSREPORT_WARNING, + bool result = reportCompileErrorNumberVA(nullptr, currentToken().pos.begin, JSREPORT_WARNING, errorNumber, args); va_end(args); return result; } bool -TokenStream::reportExtraWarningErrorNumberVA(uint32_t offset, unsigned errorNumber, va_list args) +TokenStream::reportExtraWarningErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset, + unsigned errorNumber, va_list args) { if (!options().extraWarningsOption) return true; - return reportCompileErrorNumberVA(offset, JSREPORT_STRICT|JSREPORT_WARNING, errorNumber, args); + return reportCompileErrorNumberVA(Move(notes), offset, JSREPORT_STRICT|JSREPORT_WARNING, + errorNumber, args); } void @@ -802,7 +874,34 @@ TokenStream::reportAsmJSError(uint32_t offset, unsigned errorNumber, ...) unsigned flags = options().throwOnAsmJSValidationFailureOption ? JSREPORT_ERROR : JSREPORT_WARNING; - reportCompileErrorNumberVA(offset, flags, errorNumber, args); + reportCompileErrorNumberVA(nullptr, offset, flags, errorNumber, args); + va_end(args); +} + +void +TokenStream::error(unsigned errorNumber, ...) +{ + va_list args; + va_start(args, errorNumber); +#ifdef DEBUG + bool result = +#endif + reportCompileErrorNumberVA(nullptr, currentToken().pos.begin, JSREPORT_ERROR, + errorNumber, args); + MOZ_ASSERT(!result, "reporting an error returned true?"); + va_end(args); +} + +void +TokenStream::errorAt(uint32_t offset, unsigned errorNumber, ...) +{ + va_list args; + va_start(args, errorNumber); +#ifdef DEBUG + bool result = +#endif + reportCompileErrorNumberVA(nullptr, offset, JSREPORT_ERROR, errorNumber, args); + MOZ_ASSERT(!result, "reporting an error returned true?"); va_end(args); } @@ -934,34 +1033,49 @@ TokenStream::getDirectives(bool isMultiline, bool shouldWarnDeprecated) bool TokenStream::getDirective(bool isMultiline, bool shouldWarnDeprecated, - const char* directive, int directiveLength, + const char* directive, uint8_t directiveLength, const char* errorMsgPragma, UniqueTwoByteChars* destination) { MOZ_ASSERT(directiveLength <= 18); char16_t peeked[18]; - int32_t c; if (peekChars(directiveLength, peeked) && CharsMatch(peeked, directive)) { - if (shouldWarnDeprecated && - !reportWarning(JSMSG_DEPRECATED_PRAGMA, errorMsgPragma)) - return false; + if (shouldWarnDeprecated) { + if (!warning(JSMSG_DEPRECATED_PRAGMA, errorMsgPragma)) + return false; + } skipChars(directiveLength); tokenbuf.clear(); - while ((c = peekChar()) && c != EOF && !unicode::IsSpaceOrBOM2(c)) { - getChar(); + do { + int32_t c; + if (!peekChar(&c)) + return false; + + if (c == EOF || unicode::IsSpaceOrBOM2(c)) + break; + + consumeKnownChar(c); + // Debugging directives can occur in both single- and multi-line // comments. If we're currently inside a multi-line comment, we also // need to recognize multi-line comment terminators. - if (isMultiline && c == '*' && peekChar() == '/') { - ungetChar('*'); - break; + if (isMultiline && c == '*') { + int32_t c2; + if (!peekChar(&c2)) + return false; + + if (c2 == '/') { + ungetChar('*'); + break; + } } + if (!tokenbuf.append(c)) return false; - } + } while (true); if (tokenbuf.empty()) { // The directive's URL was missing, but this is not quite an @@ -993,7 +1107,10 @@ TokenStream::getDisplayURL(bool isMultiline, bool shouldWarnDeprecated) // developer would like to refer to the source as from the source's actual // URL. - return getDirective(isMultiline, shouldWarnDeprecated, " sourceURL=", 11, + static const char sourceURLDirective[] = " sourceURL="; + constexpr uint8_t sourceURLDirectiveLength = ArrayLength(sourceURLDirective) - 1; + return getDirective(isMultiline, shouldWarnDeprecated, + sourceURLDirective, sourceURLDirectiveLength, "sourceURL", &displayURL_); } @@ -1003,7 +1120,10 @@ TokenStream::getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated) // Match comments of the form "//# sourceMappingURL=<url>" or // "/\* //# sourceMappingURL=<url> *\/" - return getDirective(isMultiline, shouldWarnDeprecated, " sourceMappingURL=", 18, + static const char sourceMappingURLDirective[] = " sourceMappingURL="; + constexpr uint8_t sourceMappingURLDirectiveLength = ArrayLength(sourceMappingURLDirective) - 1; + return getDirective(isMultiline, shouldWarnDeprecated, + sourceMappingURLDirective, sourceMappingURLDirectiveLength, "sourceMappingURL", &sourceMapURL_); } @@ -1110,36 +1230,6 @@ TokenStream::putIdentInTokenbuf(const char16_t* identStart) return true; } -bool -TokenStream::checkForKeyword(const KeywordInfo* kw, TokenKind* ttp) -{ - if (!awaitIsKeyword && kw->tokentype == TOK_AWAIT) { - if (ttp) - *ttp = TOK_NAME; - return true; - } - - if (kw->tokentype == TOK_RESERVED) - return reportError(JSMSG_RESERVED_ID, kw->chars); - - if (kw->tokentype == TOK_STRICT_RESERVED) - return reportStrictModeError(JSMSG_RESERVED_ID, kw->chars); - - // Working keyword. - *ttp = kw->tokentype; - return true; -} - -bool -TokenStream::checkForKeyword(JSAtom* atom, TokenKind* ttp) -{ - const KeywordInfo* kw = FindKeyword(atom); - if (!kw) - return true; - - return checkForKeyword(kw, ttp); -} - enum FirstCharKind { // A char16_t has the 'OneChar' kind if it, by itself, constitutes a valid // token that cannot also be a prefix of a longer token. E.g. ';' has the @@ -1363,36 +1453,18 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) length = userbuf.addressOfNextRawChar() - identStart; } - // Represent keywords as keyword tokens unless told otherwise. - if (modifier != KeywordIsName) { - if (const KeywordInfo* kw = FindKeyword(chars, length)) { - // That said, keywords can't contain escapes. (Contexts where - // keywords are treated as names, that also sometimes treat - // keywords as keywords, must manually check this requirement.) - // There are two exceptions - // 1) StrictReservedWords: These keywords need to be treated as - // names in non-strict mode. - // 2) yield is also treated as a name if it contains an escape - // sequence. The parser must handle this case separately. - if (hadUnicodeEscape && !( - (kw->tokentype == TOK_STRICT_RESERVED && !strictMode()) || - kw->tokentype == TOK_YIELD)) - { - reportError(JSMSG_ESCAPED_KEYWORD); - goto error; - } - - tp->type = TOK_NAME; - if (!checkForKeyword(kw, &tp->type)) - goto error; - if (tp->type != TOK_NAME && !hadUnicodeEscape) - goto out; + // Represent reserved words as reserved word tokens. + if (!hadUnicodeEscape) { + if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) { + tp->type = rw->tokentype; + goto out; } } JSAtom* atom = AtomizeChars(cx, chars, length); - if (!atom) + if (!atom) { goto error; + } tp->type = TOK_NAME; tp->setName(atom->asPropertyName()); goto out; @@ -1538,10 +1610,11 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) // grammar. We might not always be so permissive, so we warn // about it. if (c >= '8') { - if (!reportWarning(JSMSG_BAD_OCTAL, c == '8' ? "08" : "09")) { + if (!warning(JSMSG_BAD_OCTAL, c == '8' ? "08" : "09")) goto error; - } - goto decimal; // use the decimal scanner for the rest of the number + + // Use the decimal scanner for the rest of the number. + goto decimal; } c = getCharIgnoreEOL(); } @@ -1690,7 +1763,8 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) case '/': // Look for a single-line comment. if (matchChar('/')) { - c = peekChar(); + if (!peekChar(&c)) + goto error; if (c == '@' || c == '#') { bool shouldWarn = getChar() == '@'; if (!getDirectives(false, shouldWarn)) @@ -1757,7 +1831,8 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) RegExpFlag reflags = NoFlags; unsigned length = tokenbuf.length() + 1; while (true) { - c = peekChar(); + if (!peekChar(&c)) + goto error; if (c == 'g' && !(reflags & GlobalFlag)) reflags = RegExpFlag(reflags | GlobalFlag); else if (c == 'i' && !(reflags & IgnoreCaseFlag)) @@ -1774,7 +1849,8 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) length++; } - c = peekChar(); + if (!peekChar(&c)) + goto error; if (JS7_ISLET(c)) { char buf[2] = { '\0', '\0' }; tp->pos.begin += length + 1; @@ -1797,8 +1873,13 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) case '-': if (matchChar('-')) { - if (peekChar() == '>' && !flags.isDirtyLine) + int32_t c2; + if (!peekChar(&c2)) + goto error; + + if (c2 == '>' && !flags.isDirtyLine) goto skipline; + tp->type = TOK_DEC; } else { tp->type = matchChar('=') ? TOK_SUBASSIGN : TOK_SUB; @@ -1814,8 +1895,9 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) MOZ_CRASH("should have jumped to |out| or |error|"); out: - if (flags.hitOOM) - return reportError(JSMSG_OUT_OF_MEMORY); + if (flags.hitOOM) { + return false; + } flags.isDirtyLine = true; tp->pos.end = userbuf.offset(); @@ -1831,8 +1913,9 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) return true; error: - if (flags.hitOOM) - return reportError(JSMSG_OUT_OF_MEMORY); + if (flags.hitOOM) { + return false; + } flags.isDirtyLine = true; tp->pos.end = userbuf.offset(); @@ -1850,37 +1933,6 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } bool -TokenStream::getBracedUnicode(uint32_t* cp) -{ - consumeKnownChar('{'); - - bool first = true; - int32_t c; - uint32_t code = 0; - while (true) { - c = getCharIgnoreEOL(); - if (c == EOF) - return false; - if (c == '}') { - if (first) - return false; - break; - } - - if (!JS7_ISHEX(c)) - return false; - - code = (code << 4) | JS7_UNHEX(c); - if (code > unicode::NonBMPMax) - return false; - first = false; - } - - *cp = code; - return true; -} - -bool TokenStream::getStringOrTemplateToken(int untilChar, Token** tp) { int c; @@ -1897,11 +1949,15 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp) while ((c = getCharIgnoreEOL()) != untilChar) { if (c == EOF) { ungetCharIgnoreEOL(c); - reportError(JSMSG_UNTERMINATED_STRING); + error(JSMSG_UNTERMINATED_STRING); return false; } if (c == '\\') { + // When parsing templates, we don't immediately report errors for + // invalid escapes; these are handled by the parser. + // In those cases we don't append to tokenbuf, since it won't be + // read. switch (c = getChar()) { case 'b': c = '\b'; break; case 'f': c = '\f'; break; @@ -1917,12 +1973,73 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp) // Unicode character specification. case 'u': { - if (peekChar() == '{') { - uint32_t code; - if (!getBracedUnicode(&code)) { - reportError(JSMSG_MALFORMED_ESCAPE, "Unicode"); - return false; - } + uint32_t code = 0; + + int32_t c2; + if (!peekChar(&c2)) + return false; + + uint32_t start = userbuf.offset() - 2; + + if (c2 == '{') { + consumeKnownChar('{'); + + bool first = true; + bool valid = true; + do { + int32_t c = getCharIgnoreEOL(); + if (c == EOF) { + if (parsingTemplate) { + setInvalidTemplateEscape(start, InvalidEscapeType::Unicode); + valid = false; + break; + } + reportInvalidEscapeError(start, InvalidEscapeType::Unicode); + return false; + } + if (c == '}') { + if (first) { + if (parsingTemplate) { + setInvalidTemplateEscape(start, InvalidEscapeType::Unicode); + valid = false; + break; + } + reportInvalidEscapeError(start, InvalidEscapeType::Unicode); + return false; + } + break; + } + + if (!JS7_ISHEX(c)) { + if (parsingTemplate) { + // We put the character back so that we read + // it on the next pass, which matters if it + // was '`' or '\'. + ungetCharIgnoreEOL(c); + setInvalidTemplateEscape(start, InvalidEscapeType::Unicode); + valid = false; + break; + } + reportInvalidEscapeError(start, InvalidEscapeType::Unicode); + return false; + } + + code = (code << 4) | JS7_UNHEX(c); + if (code > unicode::NonBMPMax) { + if (parsingTemplate) { + setInvalidTemplateEscape(start + 3, InvalidEscapeType::UnicodeOverflow); + valid = false; + break; + } + reportInvalidEscapeError(start + 3, InvalidEscapeType::UnicodeOverflow); + return false; + } + + first = false; + } while (true); + + if (!valid) + continue; MOZ_ASSERT(code <= unicode::NonBMPMax); if (code < unicode::NonBMPMin) { @@ -1945,7 +2062,11 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp) c = (c << 4) + JS7_UNHEX(cp[3]); skipChars(4); } else { - reportError(JSMSG_MALFORMED_ESCAPE, "Unicode"); + if (parsingTemplate) { + setInvalidTemplateEscape(start, InvalidEscapeType::Unicode); + continue; + } + reportInvalidEscapeError(start, InvalidEscapeType::Unicode); return false; } break; @@ -1958,7 +2079,12 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp) c = (JS7_UNHEX(cp[0]) << 4) + JS7_UNHEX(cp[1]); skipChars(2); } else { - reportError(JSMSG_MALFORMED_ESCAPE, "hexadecimal"); + uint32_t start = userbuf.offset() - 2; + if (parsingTemplate) { + setInvalidTemplateEscape(start, InvalidEscapeType::Hexadecimal); + continue; + } + reportInvalidEscapeError(start, InvalidEscapeType::Hexadecimal); return false; } break; @@ -1969,13 +2095,14 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp) if (JS7_ISOCT(c)) { int32_t val = JS7_UNOCT(c); - c = peekChar(); + if (!peekChar(&c)) + return false; // Strict mode code allows only \0, then a non-digit. if (val != 0 || JS7_ISDEC(c)) { if (parsingTemplate) { - reportError(JSMSG_DEPRECATED_OCTAL); - return false; + setInvalidTemplateEscape(userbuf.offset() - 2, InvalidEscapeType::Octal); + continue; } if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL)) return false; @@ -1985,7 +2112,8 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp) if (JS7_ISOCT(c)) { val = 8 * val + JS7_UNOCT(c); getChar(); - c = peekChar(); + if (!peekChar(&c)) + return false; if (JS7_ISOCT(c)) { int32_t save = val; val = 8 * val + JS7_UNOCT(c); @@ -2003,7 +2131,7 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp) } else if (TokenBuf::isRawEOLChar(c)) { if (!parsingTemplate) { ungetCharIgnoreEOL(c); - reportError(JSMSG_UNTERMINATED_STRING); + error(JSMSG_UNTERMINATED_STRING); return false; } if (c == '\r') { diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 6ba9fba5a..2744fd144 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -26,14 +26,13 @@ #include "js/UniquePtr.h" #include "js/Vector.h" #include "vm/RegExpObject.h" +#include "vm/String.h" struct KeywordInfo; namespace js { namespace frontend { -class AutoAwaitIsKeyword; - struct TokenPos { uint32_t begin; // Offset of the token's first char. uint32_t end; // Offset of 1 past the token's last char. @@ -80,6 +79,20 @@ struct TokenPos { enum DecimalPoint { NoDecimal = false, HasDecimal = true }; +enum class InvalidEscapeType { + // No invalid character escapes. + None, + // A malformed \x escape. + Hexadecimal, + // A malformed \u escape. + Unicode, + // An otherwise well-formed \u escape which represents a + // codepoint > 10FFFF. + UnicodeOverflow, + // An octal escape in a template token. + Octal +}; + class TokenStream; struct Token @@ -106,9 +119,6 @@ struct Token // TOK_DIV. Operand, - // Treat keywords as names by returning TOK_NAME. - KeywordIsName, - // Treat subsequent characters as the tail of a template literal, after // a template substitution, beginning with a "}", continuing with zero // or more template literal characters, and ending with either "${" or @@ -150,10 +160,6 @@ struct Token // If a semicolon is inserted automatically, the next token is already // gotten with None, but we expect Operand. OperandIsNone, - - // If name of method definition is `get` or `set`, the next token is - // already gotten with KeywordIsName, but we expect None. - NoneIsKeywordIsName, }; friend class TokenStream; @@ -210,11 +216,6 @@ struct Token return u.name->JSAtom::asPropertyName(); // poor-man's type verification } - bool nameContainsEscape() const { - PropertyName* n = name(); - return pos.begin + n->length() != pos.end; - } - JSAtom* atom() const { MOZ_ASSERT(type == TOK_STRING || type == TOK_TEMPLATE_HEAD || @@ -240,10 +241,22 @@ struct Token }; class CompileError : public JSErrorReport { -public: + public: void throwError(JSContext* cx); }; +extern const char* +ReservedWordToCharZ(PropertyName* str); + +extern MOZ_MUST_USE bool +IsFutureReservedWord(JSLinearString* str); + +extern MOZ_MUST_USE bool +IsReservedWordLiteral(JSLinearString* str); + +extern MOZ_MUST_USE bool +IsStrictReservedWord(JSLinearString* str); + // Ideally, tokenizing would be entirely independent of context. But the // strict mode flag, which is in SharedContext, affects tokenizing, and // TokenStream needs to see it. @@ -330,25 +343,26 @@ class MOZ_STACK_CLASS TokenStream JSVersion versionNumber() const { return VersionNumber(options().version); } JSVersion versionWithFlags() const { return options().version; } + private: + PropertyName* reservedWordToPropertyName(TokenKind tt) const; + + public: PropertyName* currentName() const { - if (isCurrentTokenType(TOK_YIELD)) - return cx->names().yield; - MOZ_ASSERT(isCurrentTokenType(TOK_NAME)); - return currentToken().name(); + if (isCurrentTokenType(TOK_NAME)) { + return currentToken().name(); + } + + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(currentToken().type)); + return reservedWordToPropertyName(currentToken().type); } PropertyName* nextName() const { - if (nextToken().type == TOK_YIELD) - return cx->names().yield; - MOZ_ASSERT(nextToken().type == TOK_NAME); - return nextToken().name(); - } + if (nextToken().type != TOK_NAME) { + return nextToken().name(); + } - bool nextNameContainsEscape() const { - if (nextToken().type == TOK_YIELD) - return false; - MOZ_ASSERT(nextToken().type == TOK_NAME); - return nextToken().nameContainsEscape(); + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(nextToken().type)); + return reservedWordToPropertyName(nextToken().type); } bool isCurrentTokenAssignment() const { @@ -361,21 +375,47 @@ class MOZ_STACK_CLASS TokenStream bool hadError() const { return flags.hadError; } void clearSawOctalEscape() { flags.sawOctalEscape = false; } + bool hasInvalidTemplateEscape() const { + return invalidTemplateEscapeType != InvalidEscapeType::None; + } + void clearInvalidTemplateEscape() { + invalidTemplateEscapeType = InvalidEscapeType::None; + } + + // If there is an invalid escape in a template, report it and return false, + // otherwise return true. + bool checkForInvalidTemplateEscapeError() { + if (invalidTemplateEscapeType == InvalidEscapeType::None) + return true; + + reportInvalidEscapeError(invalidTemplateEscapeOffset, invalidTemplateEscapeType); + return false; + } + // TokenStream-specific error reporters. bool reportError(unsigned errorNumber, ...); bool reportErrorNoOffset(unsigned errorNumber, ...); - bool reportWarning(unsigned errorNumber, ...); + + // Report the given error at the current offset. + void error(unsigned errorNumber, ...); + + // Report the given error at the given offset. + void errorAt(uint32_t offset, unsigned errorNumber, ...); + + // Warn at the current offset. + MOZ_MUST_USE bool warning(unsigned errorNumber, ...); static const uint32_t NoOffset = UINT32_MAX; // General-purpose error reporters. You should avoid calling these - // directly, and instead use the more succinct alternatives (e.g. - // reportError()) in TokenStream, Parser, and BytecodeEmitter. - bool reportCompileErrorNumberVA(uint32_t offset, unsigned flags, unsigned errorNumber, - va_list args); - bool reportStrictModeErrorNumberVA(uint32_t offset, bool strictMode, unsigned errorNumber, - va_list args); - bool reportExtraWarningErrorNumberVA(uint32_t offset, unsigned errorNumber, va_list args); + // directly, and instead use the more succinct alternatives (error(), + // warning(), &c.) in TokenStream, Parser, and BytecodeEmitter. + bool reportCompileErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset, unsigned flags, + unsigned errorNumber, va_list args); + bool reportStrictModeErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset, + bool strictMode, unsigned errorNumber, va_list args); + bool reportExtraWarningErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset, + unsigned errorNumber, va_list args); // asm.js reporter void reportAsmJSError(uint32_t offset, unsigned errorNumber, ...); @@ -414,6 +454,33 @@ class MOZ_STACK_CLASS TokenStream bool reportStrictModeError(unsigned errorNumber, ...); bool strictMode() const { return strictModeGetter && strictModeGetter->strictMode(); } + void setInvalidTemplateEscape(uint32_t offset, InvalidEscapeType type) { + MOZ_ASSERT(type != InvalidEscapeType::None); + if (invalidTemplateEscapeType != InvalidEscapeType::None) + return; + invalidTemplateEscapeOffset = offset; + invalidTemplateEscapeType = type; + } + void reportInvalidEscapeError(uint32_t offset, InvalidEscapeType type) { + switch (type) { + case InvalidEscapeType::None: + MOZ_ASSERT_UNREACHABLE("unexpected InvalidEscapeType"); + return; + case InvalidEscapeType::Hexadecimal: + errorAt(offset, JSMSG_MALFORMED_ESCAPE, "hexadecimal"); + return; + case InvalidEscapeType::Unicode: + errorAt(offset, JSMSG_MALFORMED_ESCAPE, "Unicode"); + return; + case InvalidEscapeType::UnicodeOverflow: + errorAt(offset, JSMSG_UNICODE_OVERFLOW, "escape sequence"); + return; + case InvalidEscapeType::Octal: + errorAt(offset, JSMSG_DEPRECATED_OCTAL); + return; + } + } + static JSAtom* atomize(ExclusiveContext* cx, CharBuffer& cb); MOZ_MUST_USE bool putIdentInTokenbuf(const char16_t* identStart); @@ -431,21 +498,19 @@ class MOZ_STACK_CLASS TokenStream {} }; - bool awaitIsKeyword = false; - friend class AutoAwaitIsKeyword; + uint32_t invalidTemplateEscapeOffset = 0; + InvalidEscapeType invalidTemplateEscapeType = InvalidEscapeType::None; public: typedef Token::Modifier Modifier; static constexpr Modifier None = Token::None; static constexpr Modifier Operand = Token::Operand; - static constexpr Modifier KeywordIsName = Token::KeywordIsName; static constexpr Modifier TemplateTail = Token::TemplateTail; typedef Token::ModifierException ModifierException; static constexpr ModifierException NoException = Token::NoException; static constexpr ModifierException NoneIsOperand = Token::NoneIsOperand; static constexpr ModifierException OperandIsNone = Token::OperandIsNone; - static constexpr ModifierException NoneIsKeywordIsName = Token::NoneIsKeywordIsName; void addModifierException(ModifierException modifierException) { #ifdef DEBUG @@ -474,10 +539,6 @@ class MOZ_STACK_CLASS TokenStream MOZ_ASSERT(next.type != TOK_DIV && next.type != TOK_REGEXP, "next token requires contextual specifier to be parsed unambiguously"); break; - case NoneIsKeywordIsName: - MOZ_ASSERT(next.modifier == KeywordIsName); - MOZ_ASSERT(next.type != TOK_NAME); - break; default: MOZ_CRASH("unexpected modifier exception"); } @@ -504,18 +565,17 @@ class MOZ_STACK_CLASS TokenStream return; } - if (lookaheadToken.modifierException == NoneIsKeywordIsName) { - // getToken() permissibly following getToken(KeywordIsName). - if (modifier == None && lookaheadToken.modifier == KeywordIsName) - return; - } - MOZ_ASSERT_UNREACHABLE("this token was previously looked up with a " "different modifier, potentially making " "tokenization non-deterministic"); #endif } + const Token& nextToken() const { + MOZ_ASSERT(hasLookahead()); + return tokens[(cursor + 1) & ntokensMask]; + } + // Advance to the next token. If the token stream encountered an error, // return false. Otherwise return true and store the token kind in |*ttp|. MOZ_MUST_USE bool getToken(TokenKind* ttp, Modifier modifier = None) { @@ -644,36 +704,6 @@ class MOZ_STACK_CLASS TokenStream MOZ_ALWAYS_TRUE(matched); } - // Like matchToken(..., TOK_NAME) but further matching the name token only - // if it has the given characters, without containing escape sequences. - // If the name token has the given characters yet *does* contain an escape, - // a syntax error will be reported. - // - // This latter behavior makes this method unsuitable for use in any context - // where ASI might occur. In such places, an escaped "contextual keyword" - // on a new line is the start of an ExpressionStatement, not a continuation - // of a StatementListItem (or ImportDeclaration or ExportDeclaration, in - // modules). - MOZ_MUST_USE bool matchContextualKeyword(bool* matchedp, Handle<PropertyName*> keyword, - Modifier modifier = None) - { - TokenKind token; - if (!getToken(&token, modifier)) - return false; - if (token == TOK_NAME && currentToken().name() == keyword) { - if (currentToken().nameContainsEscape()) { - reportError(JSMSG_ESCAPED_KEYWORD); - return false; - } - - *matchedp = true; - } else { - *matchedp = false; - ungetToken(); - } - return true; - } - MOZ_MUST_USE bool nextTokenEndsExpr(bool* endsExpr) { TokenKind tt; if (!peekToken(&tt)) @@ -739,19 +769,6 @@ class MOZ_STACK_CLASS TokenStream return sourceMapURL_.get(); } - // If |atom| is not a keyword in this version, return true with *ttp - // unchanged. - // - // If it is a reserved word in this version and strictness mode, and thus - // can't be present in correct code, report a SyntaxError and return false. - // - // If it is a keyword, like "if", return true with the keyword's TokenKind - // in *ttp. - MOZ_MUST_USE bool checkForKeyword(JSAtom* atom, TokenKind* ttp); - - // Same semantics as above, but for the provided keyword. - MOZ_MUST_USE bool checkForKeyword(const KeywordInfo* kw, TokenKind* ttp); - // This class maps a userbuf offset (which is 0-indexed) to a line number // (which is 1-indexed) and a column index (which is 0-indexed). class SourceCoords @@ -947,7 +964,6 @@ class MOZ_STACK_CLASS TokenStream MOZ_MUST_USE bool getTokenInternal(TokenKind* ttp, Modifier modifier); - MOZ_MUST_USE bool getBracedUnicode(uint32_t* code); MOZ_MUST_USE bool getStringOrTemplateToken(int untilChar, Token** tp); int32_t getChar(); @@ -964,7 +980,7 @@ class MOZ_STACK_CLASS TokenStream MOZ_MUST_USE bool getDirectives(bool isMultiline, bool shouldWarnDeprecated); MOZ_MUST_USE bool getDirective(bool isMultiline, bool shouldWarnDeprecated, - const char* directive, int directiveLength, + const char* directive, uint8_t directiveLength, const char* errorMsgPragma, UniquePtr<char16_t[], JS::FreePolicy>* destination); MOZ_MUST_USE bool getDisplayURL(bool isMultiline, bool shouldWarnDeprecated); @@ -982,29 +998,30 @@ class MOZ_STACK_CLASS TokenStream MOZ_ASSERT(c == expect); } - int32_t peekChar() { - int32_t c = getChar(); - ungetChar(c); - return c; + MOZ_MUST_USE bool peekChar(int32_t* c) { + *c = getChar(); + ungetChar(*c); + return true; } - void skipChars(int n) { - while (--n >= 0) - getChar(); + void skipChars(uint8_t n) { + while (n-- > 0) { + MOZ_ASSERT(userbuf.hasRawChars()); + mozilla::DebugOnly<int32_t> c = getCharIgnoreEOL(); + MOZ_ASSERT(c != '\n'); + } } - void skipCharsIgnoreEOL(int n) { - while (--n >= 0) + void skipCharsIgnoreEOL(uint8_t n) { + while (n-- > 0) { + MOZ_ASSERT(userbuf.hasRawChars()); getCharIgnoreEOL(); + } } void updateLineInfoForEOL(); void updateFlagsForEOL(); - const Token& nextToken() const { - MOZ_ASSERT(hasLookahead()); - return tokens[(cursor + 1) & ntokensMask]; - } bool hasLookahead() const { return lookahead > 0; } @@ -1029,25 +1046,6 @@ class MOZ_STACK_CLASS TokenStream StrictModeGetter* strictModeGetter; // used to test for strict mode }; -class MOZ_STACK_CLASS AutoAwaitIsKeyword -{ -private: - TokenStream* ts_; - bool oldAwaitIsKeyword_; - -public: - AutoAwaitIsKeyword(TokenStream* ts, bool awaitIsKeyword) { - ts_ = ts; - oldAwaitIsKeyword_ = ts_->awaitIsKeyword; - ts_->awaitIsKeyword = awaitIsKeyword; - } - - ~AutoAwaitIsKeyword() { - ts_->awaitIsKeyword = oldAwaitIsKeyword_; - ts_ = nullptr; - } -}; - extern const char* TokenKindToDesc(TokenKind tt); |