Bug 1377007 - Implementing basic binjs-ref parser in SpiderMonkey;r=arai,jorendorff

This patch implements a Binary AST parser matching the latest
binjs-ref parser at this date. The subset of JS recognized matches
ES5, with an AST based on a slightly customized Babylon AST.

At this stage, the parser trusts its input, insofar as it does not
check directives or bindings. Followup patch will introduce checking
of these directives/bindings.

MozReview-Commit-ID: 1nt230rt02R

--HG--
extra : rebase_source : 4710455326bfd028f6e396426536be01f16dd649
This commit is contained in:
David Teller 2017-09-11 16:54:48 +02:00
Родитель 4382d682b7
Коммит 5db396578d
14 изменённых файлов: 3113 добавлений и 116 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

278
js/src/frontend/BinSource.h Normal file
Просмотреть файл

@ -0,0 +1,278 @@
#ifndef frontend_BinSource_h
#define frontend_BinSource_h
/**
* A Binary AST parser.
*
* At the time of this writing, this parser implements the grammar of ES5
* and trusts its input (in particular, variable declarations).
*/
#include "mozilla/Maybe.h"
#include "frontend/BinTokenReaderTester.h"
#include "frontend/FullParseHandler.h"
#include "frontend/ParseContext.h"
#include "frontend/ParseNode.h"
#include "frontend/SharedContext.h"
#include "js/GCHashTable.h"
#include "js/GCVector.h"
#include "js/Result.h"
namespace js {
namespace frontend {
class BinASTParser;
/**
* The parser for a Binary AST.
*
* By design, this parser never needs to backtrack or look ahead. Errors are not
* recoverable.
*/
class BinASTParser : private JS::AutoGCRooter, public ErrorReporter
{
using Names = JS::GCVector<JSString*, 8>;
using Tokenizer = BinTokenReaderTester;
using Chars = Tokenizer::Chars;
public:
BinASTParser(JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames, const JS::ReadOnlyCompileOptions& options)
: AutoGCRooter(cx, BINPARSER)
, traceListHead_(nullptr)
, options_(options)
, cx_(cx)
, alloc_(alloc)
, nodeAlloc_(cx, alloc)
, keepAtoms_(cx)
, parseContext_(nullptr)
, usedNames_(usedNames)
, factory_(cx, alloc, nullptr, SourceKind::Binary)
{
cx_->frontendCollectionPool().addActiveCompilation();
tempPoolMark_ = alloc.mark();
}
~BinASTParser()
{
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();
cx_->frontendCollectionPool().removeActiveCompilation();
}
/**
* Parse a buffer, returning a node (which may be nullptr) in case of success
* or Nothing() in case of error.
*
* The instance of `ParseNode` MAY NOT survive the `BinASTParser`. Indeed,
* destruction of the `BinASTParser` will also destroy the `ParseNode`.
*
* In case of error, the parser reports the JS error.
*/
JS::Result<ParseNode*> parse(const uint8_t* start, const size_t length);
JS::Result<ParseNode*> parse(const Vector<uint8_t>& data);
private:
MOZ_MUST_USE JS::Result<ParseNode*> parseAux(const uint8_t* start, const size_t length);
// --- Raise errors.
//
// These methods return a (failed) JS::Result for convenience.
MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseInvalidKind(const char* superKind, const BinKind kind);
MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseInvalidField(const char* kind, const BinField field);
MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseInvalidEnum(const char* kind, const Chars& value);
MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseMissingField(const char* kind, const BinField field);
MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseEmpty(const char* description);
MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseOOM();
MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseError(const char* description);
MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseError(BinKind kind, const char* description);
// Ensure that this parser will never be used again.
void poison();
// --- Parse full nodes (methods are sorted by alphabetical order)
//
// These method may NEVER return `nullptr`. // FIXME: We can probably optimize Result<> based on this.
MOZ_MUST_USE JS::Result<ParseNode*> parseBlockStatement();
MOZ_MUST_USE JS::Result<ParseNode*> parseCatchClause();
MOZ_MUST_USE JS::Result<ParseNode*> parseExpression();
MOZ_MUST_USE JS::Result<ParseNode*> parseForInit();
MOZ_MUST_USE JS::Result<ParseNode*> parseForInInit();
MOZ_MUST_USE JS::Result<ParseNode*> parseIdentifier();
MOZ_MUST_USE JS::Result<ParseNode*> parseObjectPropertyName();
MOZ_MUST_USE JS::Result<ParseNode*> parseObjectMember();
MOZ_MUST_USE JS::Result<ParseNode*> parsePattern(); // Parse a *binding* pattern.
MOZ_MUST_USE JS::Result<ParseNode*> parsePropertyName();
MOZ_MUST_USE JS::Result<ParseNode*> parseProgram();
MOZ_MUST_USE JS::Result<ParseNode*> parseStatement();
MOZ_MUST_USE JS::Result<ParseNode*> parseSwitchCase();
MOZ_MUST_USE JS::Result<ParseNode*> parseVariableDeclarator();
// --- Parse lists of nodes (methods are sorted by alphabetical order)
MOZ_MUST_USE JS::Result<ParseNode*> parseArgumentList();
MOZ_MUST_USE JS::Result<ParseNode*> parseDirectiveList();
MOZ_MUST_USE JS::Result<ParseNode*> parseExpressionList(bool acceptElisions);
// Returns a list of PNK_COLON.
MOZ_MUST_USE JS::Result<ParseNode*> parseObjectMemberList();
MOZ_MUST_USE JS::Result<ParseNode*> parseStatementList();
MOZ_MUST_USE JS::Result<ParseNode*> parseSwitchCaseList();
// --- Parse the contents of a node whose kind has already been determined.
MOZ_MUST_USE JS::Result<ParseNode*> parseArrayExpressionAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parseBreakOrContinueStatementAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parseBlockStatementAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parseExpressionStatementAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parseExpressionAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parseFunctionAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parseIdentifierAux(const BinKind, const Tokenizer::BinFields& fields, const bool expectObjectPropertyName = false);
MOZ_MUST_USE JS::Result<ParseNode*> parseMemberExpressionAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parseNumericLiteralAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parseObjectExpressionAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parsePatternAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parseStringLiteralAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parseStatementAux(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<ParseNode*> parseVariableDeclarationAux(const BinKind kind, const Tokenizer::BinFields& fields);
// --- Auxiliary parsing functions that may have a side-effect on the parser but do not return a node.
MOZ_MUST_USE JS::Result<Ok> checkEmptyTuple(const BinKind kind, const Tokenizer::BinFields& fields);
MOZ_MUST_USE JS::Result<Ok> parseElisionAux(const BinKind kind, const Tokenizer::BinFields& fields);
// Parse full scope information to the current innermost scope.
MOZ_MUST_USE JS::Result<Ok> parseAndUpdateCurrentScope();
// Parse full scope information to a specific var scope / let scope combination.
MOZ_MUST_USE JS::Result<Ok> parseAndUpdateScope(ParseContext::Scope& varScope, ParseContext::Scope& letScope);
// Parse a list of names and add it to a given scope.
MOZ_MUST_USE JS::Result<Ok> parseAndUpdateScopeNames(ParseContext::Scope& scope, DeclarationKind kind);
MOZ_MUST_USE JS::Result<Ok> parseStringList(MutableHandle<Maybe<Names>> out);
// --- Utilities.
MOZ_MUST_USE JS::Result<ParseNode*> appendDirectivesToBody(ParseNode* body, ParseNode* directives);
// Read a string as a `Chars`.
MOZ_MUST_USE JS::Result<Ok> readString(Maybe<Chars>& out);
MOZ_MUST_USE JS::Result<Ok> readString(MutableHandleAtom out);
MOZ_MUST_USE JS::Result<bool> readBool();
MOZ_MUST_USE JS::Result<double> readNumber();
const ReadOnlyCompileOptions& options() const override {
return this->options_;
}
// Names
bool hasUsedName(HandlePropertyName name);
// --- GC.
void trace(JSTracer* trc) {
ObjectBox::TraceList(trc, traceListHead_);
}
public:
ObjectBox* newObjectBox(JSObject* obj) {
MOZ_ASSERT(obj);
/*
* We use JSContext.tempLifoAlloc to allocate parsed objects and place them
* on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
* arenas containing the entries must be alive until we are done with
* scanning, parsing and code generation for the whole script or top-level
* function.
*/
ObjectBox* objbox = alloc_.new_<ObjectBox>(obj, traceListHead_);
if (!objbox) {
ReportOutOfMemory(cx_);
return nullptr;
}
traceListHead_ = objbox;
return objbox;
}
ParseNode* allocParseNode(size_t size) {
MOZ_ASSERT(size == sizeof(ParseNode));
return static_cast<ParseNode*>(nodeAlloc_.allocNode());
}
JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline)
private: // Implement ErrorReporter
virtual void lineAndColumnAt(size_t offset, uint32_t* line, uint32_t* column) const override {
*line = 0;
*column = offset;
}
virtual void currentLineAndColumn(uint32_t* line, uint32_t* column) const override {
*line = 0;
*column = offset();
}
size_t offset() const {
if (tokenizer_.isSome())
return tokenizer_->offset();
return 0;
}
virtual bool hasTokenizationStarted() const override {
return tokenizer_.isSome();
}
virtual void reportErrorNoOffsetVA(unsigned errorNumber, va_list args) override;
virtual const char* getFilename() const override {
return this->options_.filename();
}
ObjectBox* traceListHead_;
const ReadOnlyCompileOptions& options_;
JSContext* cx_;
LifoAlloc& alloc_;
LifoAlloc::Mark tempPoolMark_;
ParseNodeAllocator nodeAlloc_;
// Root atoms and objects allocated for the parse tree.
AutoKeepAtoms keepAtoms_;
// The current ParseContext, holding directives, etc.
ParseContext* parseContext_;
UsedNameTracker& usedNames_;
Maybe<Tokenizer> tokenizer_;
FullParseHandler factory_;
friend class BinParseContext;
// Needs access to AutoGCRooter.
friend void TraceBinParser(JSTracer* trc, AutoGCRooter* parser);
};
class BinParseContext : public ParseContext
{
public:
BinParseContext(JSContext* cx, BinASTParser* parser, SharedContext* sc, Directives* newDirectives)
: ParseContext(cx, parser->parseContext_, sc, *parser,
parser->usedNames_, newDirectives, /* isFull = */ true)
{ }
};
} // namespace frontend
} // namespace js
#endif // frontend_BinSource_h

Просмотреть файл

@ -31,8 +31,7 @@ bool
BinTokenReaderTester::raiseError(const char* description)
{
MOZ_ASSERT(!cx_->isExceptionPending());
TokenPos pos;
latestTokenPos(pos);
TokenPos pos = this->pos();
JS_ReportErrorASCII(cx_, "BinAST parsing error: %s at offsets %u => %u",
description, pos.begin, pos.end);
return false;
@ -416,12 +415,20 @@ BinTokenReaderTester::offset() const
return latestKnownGoodPos_;
}
void
BinTokenReaderTester::latestTokenPos(TokenPos& pos)
TokenPos
BinTokenReaderTester::pos()
{
pos.begin = latestKnownGoodPos_;
return pos(latestKnownGoodPos_);
}
TokenPos
BinTokenReaderTester::pos(size_t start)
{
TokenPos pos;
pos.begin = start;
pos.end = current_ - start_;
MOZ_ASSERT(pos.end >= pos.begin);
return pos;
}
void

Просмотреть файл

@ -181,7 +181,8 @@ class MOZ_STACK_CLASS BinTokenReaderTester
/**
* Return the position of the latest token.
*/
void latestTokenPos(TokenPos& out);
TokenPos pos();
TokenPos pos(size_t startOffset);
size_t offset() const;
/**

Просмотреть файл

@ -124,10 +124,13 @@ IsKeyword(JSLinearString* str);
void
TraceParser(JSTracer* trc, JS::AutoGCRooter* parser);
#if defined(JS_BUILD_BINAST)
/* Trace all GC things reachable from binjs parser. Defined in BinSource.cpp. */
void
TraceBinParser(JSTracer* trc, JS::AutoGCRooter* parser);
#endif // defined(JS_BUILD_BINAST)
class MOZ_STACK_CLASS AutoFrontendTraceLog
{

Просмотреть файл

@ -21,6 +21,13 @@ class RegExpObject;
namespace frontend {
enum class SourceKind {
// We are parsing from a text source (Parser.h)
Text,
// We are parsing from a binary source (BinSource.h)
Binary,
};
// Parse handler used when generating a full parse tree for all code which the
// parser encounters.
class FullParseHandler
@ -41,6 +48,8 @@ class FullParseHandler
size_t lazyInnerFunctionIndex;
size_t lazyClosedOverBindingIndex;
const SourceKind sourceKind_;
public:
/* new_ methods for creating parse nodes. These report OOM on context. */
JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline)
@ -68,15 +77,24 @@ class FullParseHandler
return node->isInParens() && (node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY));
}
FullParseHandler(JSContext* cx, LifoAlloc& alloc, LazyScript* lazyOuterFunction)
FullParseHandler(JSContext* cx, LifoAlloc& alloc, LazyScript* lazyOuterFunction,
SourceKind kind = SourceKind::Text)
: allocator(cx, alloc),
lazyOuterFunction_(cx, lazyOuterFunction),
lazyInnerFunctionIndex(0),
lazyClosedOverBindingIndex(0)
lazyClosedOverBindingIndex(0),
sourceKind_(SourceKind::Text)
{}
static ParseNode* null() { return nullptr; }
// The FullParseHandler may be used to create nodes for text sources
// (from Parser.h) or for binary sources (from BinSource.h). In the latter
// case, some common assumptions on offsets are incorrect, e.g. in `a + b`,
// `a`, `b` and `+` may be stored in any order. We use `sourceKind()`
// to determine whether we need to check these assumptions.
SourceKind sourceKind() const { return sourceKind_; }
ParseNode* freeTree(ParseNode* pn) { return allocator.freeTree(pn); }
void prepareNodeForMutation(ParseNode* pn) { return allocator.prepareNodeForMutation(pn); }
@ -230,8 +248,7 @@ class FullParseHandler
ParseNode* elision = new_<NullaryNode>(PNK_ELISION, pos);
if (!elision)
return false;
literal->append(elision);
addList(/* list = */ literal, /* child = */ elision);
literal->pn_xflags |= PNX_ARRAYHOLESPREAD | PNX_NONCONST;
return true;
}
@ -243,8 +260,7 @@ class FullParseHandler
ParseNode* spread = newSpread(begin, inner);
if (!spread)
return false;
literal->append(spread);
addList(/* list = */ literal, /* child = */ spread);
literal->pn_xflags |= PNX_ARRAYHOLESPREAD | PNX_NONCONST;
return true;
}
@ -254,8 +270,7 @@ class FullParseHandler
if (!element->isConstant())
literal->pn_xflags |= PNX_NONCONST;
literal->append(element);
addList(/* list = */ literal, /* child = */ element);
}
ParseNode* newCall(const TokenPos& pos) {
@ -294,7 +309,9 @@ class FullParseHandler
ParseNode* newSuperBase(ParseNode* thisName, const TokenPos& pos) {
return new_<UnaryNode>(PNK_SUPERBASE, pos, thisName);
}
ParseNode* newCatchBlock(ParseNode* catchName, ParseNode* catchGuard, ParseNode* catchBody) {
return new_<TernaryNode>(PNK_CATCH, catchName, catchGuard, catchBody);
}
MOZ_MUST_USE bool addPrototypeMutation(ParseNode* literal, uint32_t begin, ParseNode* expr) {
MOZ_ASSERT(literal->isKind(PNK_OBJECT));
MOZ_ASSERT(literal->isArity(PN_LIST));
@ -306,22 +323,19 @@ class FullParseHandler
ParseNode* mutation = newUnary(PNK_MUTATEPROTO, begin, expr);
if (!mutation)
return false;
literal->append(mutation);
addList(/* list = */ literal, /* child = */ mutation);
return true;
}
MOZ_MUST_USE bool addPropertyDefinition(ParseNode* literal, ParseNode* key, ParseNode* val) {
MOZ_ASSERT(literal->isKind(PNK_OBJECT));
MOZ_ASSERT(literal->isArity(PN_LIST));
MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
key->isKind(PNK_STRING) ||
key->isKind(PNK_COMPUTED_NAME));
MOZ_ASSERT(isUsableAsObjectPropertyName(key));
ParseNode* propdef = newBinary(PNK_COLON, key, val, JSOP_INITPROP);
if (!propdef)
return false;
literal->append(propdef);
addList(/* list = */ literal, /* child = */ propdef);
return true;
}
@ -336,7 +350,7 @@ class FullParseHandler
ParseNode* propdef = newBinary(PNK_SHORTHAND, name, expr, JSOP_INITPROP);
if (!propdef)
return false;
literal->append(propdef);
addList(/* list = */ literal, /* child = */ propdef);
return true;
}
@ -348,26 +362,21 @@ class FullParseHandler
ParseNode* spread = newSpread(begin, inner);
if (!spread)
return false;
literal->append(spread);
addList(/* list = */ literal, /* child = */ spread);
return true;
}
MOZ_MUST_USE bool addObjectMethodDefinition(ParseNode* literal, ParseNode* key, ParseNode* fn,
AccessorType atype)
{
MOZ_ASSERT(literal->isKind(PNK_OBJECT));
MOZ_ASSERT(literal->isArity(PN_LIST));
MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
key->isKind(PNK_STRING) ||
key->isKind(PNK_COMPUTED_NAME));
literal->pn_xflags |= PNX_NONCONST;
ParseNode* propdef = newBinary(PNK_COLON, key, fn, AccessorTypeToJSOp(atype));
ParseNode* propdef = newObjectMethodOrPropertyDefinition(key, fn, atype);
if (!propdef)
return false;
literal->append(propdef);
literal->pn_xflags |= PNX_NONCONST;
addList(/* list = */ literal, /* child = */ propdef);
return true;
}
@ -375,15 +384,12 @@ class FullParseHandler
AccessorType atype, bool isStatic)
{
MOZ_ASSERT(methodList->isKind(PNK_CLASSMETHODLIST));
MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
key->isKind(PNK_STRING) ||
key->isKind(PNK_COMPUTED_NAME));
MOZ_ASSERT(isUsableAsObjectPropertyName(key));
ParseNode* classMethod = new_<ClassMethod>(key, fn, AccessorTypeToJSOp(atype), isStatic);
if (!classMethod)
return false;
methodList->append(classMethod);
addList(/* list = */ methodList, /* child = */ classMethod);
return true;
}
@ -422,7 +428,7 @@ class FullParseHandler
void addStatementToList(ParseNode* list, ParseNode* stmt) {
MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
list->append(stmt);
addList(/* list = */ list, /* child = */ stmt);
if (isFunctionStmt(stmt)) {
// PNX_FUNCDEFS notifies the emitter that the block contains
@ -442,12 +448,16 @@ class FullParseHandler
MOZ_ASSERT(casepn->isKind(PNK_CASE));
MOZ_ASSERT(casepn->pn_right->isKind(PNK_STATEMENTLIST));
list->append(casepn);
addList(/* list = */ list, /* child = */ casepn);
if (casepn->pn_right->pn_xflags & PNX_FUNCDEFS)
list->pn_xflags |= PNX_FUNCDEFS;
}
MOZ_MUST_USE inline bool addCatchBlock(ParseNode* catchList, ParseNode* lexicalScope,
ParseNode* catchName, ParseNode* catchGuard,
ParseNode* catchBody);
MOZ_MUST_USE bool prependInitialYield(ParseNode* stmtList, ParseNode* genName) {
MOZ_ASSERT(stmtList->isKind(PNK_STATEMENTLIST));
MOZ_ASSERT(stmtList->isArity(PN_LIST));
@ -459,8 +469,7 @@ class FullParseHandler
MOZ_ASSERT(genName->getOp() == JSOP_GETNAME);
genName->setOp(JSOP_SETNAME);
ParseNode* genInit = newBinary(PNK_ASSIGN, genName, makeGen);
ParseNode* genInit = newAssignment(PNK_ASSIGN, /* lhs = */ genName, /* rhs = */ makeGen);
if (!genInit)
return false;
@ -671,6 +680,20 @@ class FullParseHandler
!node->pn_funbox->isArrow();
}
ParseNode* newObjectMethodOrPropertyDefinition(ParseNode* key, ParseNode* fn, AccessorType atype) {
MOZ_ASSERT(isUsableAsObjectPropertyName(key));
return newBinary(PNK_COLON, key, fn, AccessorTypeToJSOp(atype));
}
bool setComprehensionLambdaBody(ParseNode* pn, ParseNode* body) {
MOZ_ASSERT(body->isKind(PNK_STATEMENTLIST));
ParseNode* paramsBody = newList(PNK_PARAMSBODY, body);
if (!paramsBody)
return false;
setFunctionFormalParametersAndBody(pn, paramsBody);
return true;
}
void setFunctionFormalParametersAndBody(ParseNode* funcNode, ParseNode* kid) {
MOZ_ASSERT_IF(kid, kid->isKind(PNK_PARAMSBODY));
funcNode->pn_body = kid;
@ -681,11 +704,11 @@ class FullParseHandler
funbox->functionNode = pn;
}
void addFunctionFormalParameter(ParseNode* pn, ParseNode* argpn) {
pn->pn_body->append(argpn);
addList(/* list = */ pn->pn_body, /* child = */ argpn);
}
void setFunctionBody(ParseNode* fn, ParseNode* body) {
MOZ_ASSERT(fn->pn_body->isKind(PNK_PARAMSBODY));
fn->pn_body->append(body);
addList(/* list = */ fn->pn_body, /* child = */ body);
}
ParseNode* newModule(const TokenPos& pos) {
@ -701,7 +724,7 @@ class FullParseHandler
if (!newExpr)
return nullptr;
addList(newExpr, ctor);
addList(/* list = */ newExpr, /* child = */ ctor);
return newExpr;
}
@ -744,6 +767,13 @@ class FullParseHandler
return node->isKind(PNK_SUPERBASE);
}
bool isUsableAsObjectPropertyName(ParseNode* node) {
return node->isKind(PNK_NUMBER)
|| node->isKind(PNK_OBJECT_PROPERTY_NAME)
|| node->isKind(PNK_STRING)
|| node->isKind(PNK_COMPUTED_NAME);
}
inline MOZ_MUST_USE bool finishInitializerAssignment(ParseNode* pn, ParseNode* init);
void setBeginPosition(ParseNode* pn, ParseNode* oth) {
@ -805,7 +835,10 @@ class FullParseHandler
}
void addList(ParseNode* list, ParseNode* kid) {
list->append(kid);
if (sourceKind_ == SourceKind::Text)
list->append(kid);
else
list->appendWithoutOrderAssumption(kid);
}
void setOp(ParseNode* pn, JSOp op) {
@ -879,6 +912,19 @@ class FullParseHandler
}
};
inline bool
FullParseHandler::addCatchBlock(ParseNode* catchList, ParseNode* lexicalScope,
ParseNode* catchName, ParseNode* catchGuard,
ParseNode* catchBody)
{
ParseNode* catchpn = newCatchBlock(catchName, catchGuard, catchBody);
if (!catchpn)
return false;
addList(/* list = */ catchList, /* child = */ lexicalScope);
lexicalScope->setScopeBody(catchpn);
return true;
}
inline bool
FullParseHandler::setLastFunctionFormalParameterDefault(ParseNode* funcpn,
ParseNode* defaultValue)

Просмотреть файл

@ -0,0 +1,96 @@
#ifndef frontend_ParseContext_inl_h
#define frontend_ParseContext_inl_h
#include "frontend/ParseContext.h"
namespace js {
namespace frontend {
template <>
inline bool
ParseContext::Statement::is<ParseContext::LabelStatement>() const
{
return kind_ == StatementKind::Label;
}
template <>
inline bool
ParseContext::Statement::is<ParseContext::ClassStatement>() const
{
return kind_ == StatementKind::Class;
}
inline JS::Result<Ok, ParseContext::BreakStatementError>
ParseContext::checkBreakStatement(PropertyName* label)
{
// Labeled 'break' statements target the nearest labeled statements (could
// be any kind) with the same label. Unlabeled 'break' statements target
// the innermost loop or switch statement.
if (label) {
auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
MOZ_ASSERT(stmt);
return stmt->label() == label;
};
if (!findInnermostStatement<ParseContext::LabelStatement>(hasSameLabel))
return mozilla::Err(ParseContext::BreakStatementError::LabelNotFound);
} else {
auto isBreakTarget = [](ParseContext::Statement* stmt) {
return StatementKindIsUnlabeledBreakTarget(stmt->kind());
};
if (!findInnermostStatement(isBreakTarget))
return mozilla::Err(ParseContext::BreakStatementError::ToughBreak);
}
return Ok();
}
inline JS::Result<Ok, ParseContext::ContinueStatementError>
ParseContext::checkContinueStatement(PropertyName* label)
{
// Labeled 'continue' statements target the nearest labeled loop
// statements with the same label. Unlabeled 'continue' statements target
// the innermost loop statement.
auto isLoop = [](ParseContext::Statement* stmt) {
MOZ_ASSERT(stmt);
return StatementKindIsLoop(stmt->kind());
};
if (!label) {
// Unlabeled statement: we target the innermost loop, so make sure that
// there is an innermost loop.
if (!findInnermostStatement(isLoop))
return mozilla::Err(ParseContext::ContinueStatementError::NotInALoop);
return Ok();
}
// Labeled statement: targest the nearest labeled loop with the same label.
ParseContext::Statement* stmt = innermostStatement();
bool foundLoop = false; // True if we have encountered at least one loop.
for (;;) {
stmt = ParseContext::Statement::findNearest(stmt, isLoop);
if (!stmt)
return foundLoop ? mozilla::Err(ParseContext::ContinueStatementError::LabelNotFound)
: mozilla::Err(ParseContext::ContinueStatementError::NotInALoop);
foundLoop = true;
// Is it labeled by our label?
stmt = stmt->enclosing();
while (stmt && stmt->is<ParseContext::LabelStatement>()) {
if (stmt->as<ParseContext::LabelStatement>().label() == label)
return Ok();
stmt = stmt->enclosing();
}
}
}
}
}
#endif // frontend_ParseContext_inl_h

Просмотреть файл

@ -7,7 +7,11 @@
#ifndef frontend_ParseContext_h
#define frontend_ParseContext_h
#include "ds/Nestable.h"
#include "frontend/BytecodeCompiler.h"
#include "frontend/ErrorReporter.h"
#include "frontend/SharedContext.h"
namespace js {
@ -289,6 +293,10 @@ class ParseContext : public Nestable<ParseContext>
return declared_.acquire(pc->sc()->context);
}
bool isEmpty() const {
return declared_->all().empty();
}
DeclaredNamePtr lookupDeclaredName(JSAtom* name) {
return declared_->lookup(name);
}
@ -402,6 +410,7 @@ class ParseContext : public Nestable<ParseContext>
{
public:
explicit inline VarScope(ParserBase* parser);
explicit inline VarScope(JSContext* cx, ParseContext* pc, UsedNameTracker& usedNames);
};
private:
@ -517,6 +526,7 @@ class ParseContext : public Nestable<ParseContext>
return sc_;
}
// `true` if we are in the body of a function definition.
bool isFunctionBox() const {
return sc_->isFunctionBox();
}
@ -578,6 +588,22 @@ class ParseContext : public Nestable<ParseContext>
return *closedOverBindingsForLazy_;
}
enum class BreakStatementError {
// Unlabeled break must be inside loop or switch.
ToughBreak,
LabelNotFound,
};
// Return Err(true) if we have encountered at least one loop,
// Err(false) otherwise.
MOZ_MUST_USE inline JS::Result<Ok, BreakStatementError> checkBreakStatement(PropertyName* label);
enum class ContinueStatementError {
NotInALoop,
LabelNotFound,
};
MOZ_MUST_USE inline JS::Result<Ok, ContinueStatementError> checkContinueStatement(PropertyName* label);
// True if we are at the topmost level of a entire script or function body.
// For example, while parsing this code we would encounter f1 and f2 at
// body level, but we would not encounter f3 or f4 at body level:

Просмотреть файл

@ -13,6 +13,27 @@
#include "frontend/TokenStream.h"
#include "vm/Printer.h"
// A few notes on lifetime of ParseNode trees:
//
// - All the `ParseNode` instances MUST BE explicitly allocated in the context's `LifoAlloc`.
// This is typically implemented by the `FullParseHandler` or it can be reimplemented with
// a custom `new_`.
//
// - The tree is bulk-deallocated when the parser is deallocated. Consequently, references
// to a subtree MUST NOT exist once the parser has been deallocated.
//
// - This bulk-deallocation DOES NOT run destructors.
//
// - Instances of `LexicalScope::Data` MUST BE allocated as instances of `ParseNode`, in the same
// `LifoAlloc`. They are bulk-deallocated alongside the rest of the tree.
//
// - Instances of `JSAtom` used throughout the tree (including instances of `PropertyName`) MUST
// be kept alive by the parser. This is done through an instance of `AutoKeepAtoms` held by
// the parser.
//
// - Once the parser is deallocated, the `JSAtom` instances MAY be garbage-collected.
namespace js {
namespace frontend {
@ -598,6 +619,7 @@ class ParseNode
appendOrCreateList(ParseNodeKind kind, ParseNode* left, ParseNode* right,
FullParseHandler* handler, ParseContext* pc);
// include "ParseNode-inl.h" for these methods.
inline PropertyName* name() const;
inline JSAtom* atom() const;
@ -731,8 +753,12 @@ class ParseNode
}
void append(ParseNode* pn) {
MOZ_ASSERT(pn_arity == PN_LIST);
MOZ_ASSERT(pn->pn_pos.begin >= pn_pos.begin);
appendWithoutOrderAssumption(pn);
}
void appendWithoutOrderAssumption(ParseNode* pn) {
MOZ_ASSERT(pn_arity == PN_LIST);
pn_pos.end = pn->pn_pos.end;
*pn_tail = pn;
pn_tail = &pn->pn_next;

Просмотреть файл

@ -43,6 +43,7 @@
#include "jsatominlines.h"
#include "jsscriptinlines.h"
#include "frontend/ParseContext-inl.h"
#include "frontend/ParseNode-inl.h"
#include "vm/EnvironmentObject-inl.h"
@ -1714,7 +1715,7 @@ Parser<FullParseHandler, CharT>::checkStatementsEOF()
}
template <typename Scope>
static typename Scope::Data*
typename Scope::Data*
NewEmptyBindingData(JSContext* cx, LifoAlloc& alloc, uint32_t numBindings)
{
size_t allocSize = Scope::sizeOfData(numBindings);
@ -1728,8 +1729,9 @@ NewEmptyBindingData(JSContext* cx, LifoAlloc& alloc, uint32_t numBindings)
}
Maybe<GlobalScope::Data*>
ParserBase::newGlobalScopeData(ParseContext::Scope& scope)
NewGlobalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc)
{
Vector<BindingName> funs(context);
Vector<BindingName> vars(context);
Vector<BindingName> lets(context);
@ -1792,8 +1794,14 @@ ParserBase::newGlobalScopeData(ParseContext::Scope& scope)
return Some(bindings);
}
Maybe<GlobalScope::Data*>
ParserBase::newGlobalScopeData(ParseContext::Scope& scope)
{
return NewGlobalScopeData(context, scope, alloc, pc);
}
Maybe<ModuleScope::Data*>
ParserBase::newModuleScopeData(ParseContext::Scope& scope)
NewModuleScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc)
{
Vector<BindingName> imports(context);
Vector<BindingName> vars(context);
@ -1858,8 +1866,14 @@ ParserBase::newModuleScopeData(ParseContext::Scope& scope)
return Some(bindings);
}
Maybe<ModuleScope::Data*>
ParserBase::newModuleScopeData(ParseContext::Scope& scope)
{
return NewModuleScopeData(context, scope, alloc, pc);
}
Maybe<EvalScope::Data*>
ParserBase::newEvalScopeData(ParseContext::Scope& scope)
NewEvalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc)
{
Vector<BindingName> funs(context);
Vector<BindingName> vars(context);
@ -1902,8 +1916,14 @@ ParserBase::newEvalScopeData(ParseContext::Scope& scope)
return Some(bindings);
}
Maybe<EvalScope::Data*>
ParserBase::newEvalScopeData(ParseContext::Scope& scope)
{
return NewEvalScopeData(context, scope, alloc, pc);
}
Maybe<FunctionScope::Data*>
ParserBase::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterExprs)
NewFunctionScopeData(JSContext* context, ParseContext::Scope& scope, bool hasParameterExprs, LifoAlloc& alloc, ParseContext* pc)
{
Vector<BindingName> positionalFormals(context);
Vector<BindingName> formals(context);
@ -1996,8 +2016,14 @@ ParserBase::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterEx
return Some(bindings);
}
Maybe<FunctionScope::Data*>
ParserBase::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterExprs)
{
return NewFunctionScopeData(context, scope, hasParameterExprs, alloc, pc);
}
Maybe<VarScope::Data*>
ParserBase::newVarScopeData(ParseContext::Scope& scope)
NewVarScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc)
{
Vector<BindingName> vars(context);
@ -2028,8 +2054,14 @@ ParserBase::newVarScopeData(ParseContext::Scope& scope)
return Some(bindings);
}
Maybe<VarScope::Data*>
ParserBase::newVarScopeData(ParseContext::Scope& scope)
{
return NewVarScopeData(context, scope, alloc, pc);
}
Maybe<LexicalScope::Data*>
ParserBase::newLexicalScopeData(ParseContext::Scope& scope)
NewLexicalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc)
{
Vector<BindingName> lets(context);
Vector<BindingName> consts(context);
@ -2079,6 +2111,12 @@ ParserBase::newLexicalScopeData(ParseContext::Scope& scope)
return Some(bindings);
}
Maybe<LexicalScope::Data*>
ParserBase::newLexicalScopeData(ParseContext::Scope& scope)
{
return NewLexicalScopeData(context, scope, alloc, pc);
}
template <>
SyntaxParseHandler::Node
PerHandlerParser<SyntaxParseHandler>::finishLexicalScope(ParseContext::Scope& scope, Node body)
@ -6513,44 +6551,16 @@ GeneralParser<ParseHandler, CharT>::continueStatement(YieldHandling yieldHandlin
if (!matchLabel(yieldHandling, &label))
return null();
// Labeled 'continue' statements target the nearest labeled loop
// statements with the same label. Unlabeled 'continue' statements target
// the innermost loop statement.
auto isLoop = [](ParseContext::Statement* stmt) {
return StatementKindIsLoop(stmt->kind());
};
if (label) {
ParseContext::Statement* stmt = pc->innermostStatement();
bool foundLoop = false;
for (;;) {
stmt = ParseContext::Statement::findNearest(stmt, isLoop);
if (!stmt) {
if (foundLoop)
error(JSMSG_LABEL_NOT_FOUND);
else
errorAt(begin, JSMSG_BAD_CONTINUE);
return null();
}
foundLoop = true;
// Is it labeled by our label?
bool foundTarget = false;
stmt = stmt->enclosing();
while (stmt && stmt->is<ParseContext::LabelStatement>()) {
if (stmt->as<ParseContext::LabelStatement>().label() == label) {
foundTarget = true;
break;
}
stmt = stmt->enclosing();
}
if (foundTarget)
break;
auto validity = pc->checkContinueStatement(label);
if (validity.isErr()) {
switch (validity.unwrapErr()) {
case ParseContext::ContinueStatementError::NotInALoop:
errorAt(begin, JSMSG_BAD_CONTINUE);
break;
case ParseContext::ContinueStatementError::LabelNotFound:
error(JSMSG_LABEL_NOT_FOUND);
break;
}
} else if (!pc->findInnermostStatement(isLoop)) {
error(JSMSG_BAD_CONTINUE);
return null();
}

Просмотреть файл

@ -204,20 +204,6 @@ public:
{ }
};
template <>
inline bool
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()
@ -439,6 +425,13 @@ ParseContext::VarScope::VarScope(ParserBase* parser)
useAsVarScope(parser->pc);
}
inline
ParseContext::VarScope::VarScope(JSContext* cx, ParseContext* pc, UsedNameTracker& usedNames)
: Scope(cx, pc, usedNames)
{
useAsVarScope(pc);
}
enum FunctionCallBehavior {
PermitAssignmentToFunctionCalls,
ForbidAssignmentToFunctionCalls
@ -1588,6 +1581,21 @@ class MOZ_STACK_CLASS AutoAwaitIsKeyword
}
};
template <typename Scope>
extern typename Scope::Data*
NewEmptyBindingData(JSContext* cx, LifoAlloc& alloc, uint32_t numBindings);
Maybe<GlobalScope::Data*>
NewGlobalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc);
Maybe<EvalScope::Data*>
NewEvalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc);
Maybe<FunctionScope::Data*>
NewFunctionScopeData(JSContext* context, ParseContext::Scope& scope, bool hasParameterExprs, LifoAlloc& alloc, ParseContext* pc);
Maybe<VarScope::Data*>
NewVarScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc);
Maybe<LexicalScope::Data*>
NewLexicalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc);
} /* namespace frontend */
} /* namespace js */

Просмотреть файл

@ -15,12 +15,14 @@
#include "builtin/ModuleObject.h"
#include "ds/InlineTable.h"
#include "frontend/ParseNode.h"
#include "frontend/TokenStream.h"
#include "vm/EnvironmentObject.h"
namespace js {
namespace frontend {
class ParseContext;
class ParseNode;
enum class StatementKind : uint8_t

Просмотреть файл

@ -86,17 +86,6 @@ JS::RootingContext::traceStackRoots(JSTracer* trc)
TraceStackRoots(trc, stackRoots_);
}
namespace js {
namespace frontend {
// Placeholder implementation.
void
TraceBinParser(JSTracer*, JS::AutoGCRooter*)
{ }
}
}
static void
TraceExactStackRoots(const CooperatingContext& target, JSTracer* trc)
{
@ -571,4 +560,3 @@ JS::AddPersistentRoot(JSRuntime* rt, RootKind kind, PersistentRooted<void*>* roo
{
rt->heapRoots.ref()[kind].insertBack(root);
}

Просмотреть файл

@ -648,7 +648,10 @@ if CONFIG['JS_BUILD_BINAST']:
# specification.
SOURCES += ['frontend/BinTokenReaderTester.cpp']
# These parts of BinAST should eventually move to release.
SOURCES += ['frontend/BinToken.cpp']
SOURCES += [
'frontend/BinSource.cpp',
'frontend/BinToken.cpp'
]
# Wasm code should use WASM_HUGE_MEMORY instead of JS_CODEGEN_X64
# so that it is easy to use the huge-mapping optimization for other