| // Copyright 2011 the V8 project authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <cmath> | |
| #include "src/allocation.h" | |
| #include "src/base/logging.h" | |
| #include "src/conversions-inl.h" | |
| #include "src/conversions.h" | |
| #include "src/globals.h" | |
| #include "src/parsing/duplicate-finder.h" | |
| #include "src/parsing/parser-base.h" | |
| #include "src/parsing/preparsed-scope-data.h" | |
| #include "src/parsing/preparser.h" | |
| #include "src/unicode.h" | |
| #include "src/utils.h" | |
| namespace v8 { | |
| namespace internal { | |
| // ---------------------------------------------------------------------------- | |
| // The CHECK_OK macro is a convenient macro to enforce error | |
| // handling for functions that may fail (by returning !*ok). | |
| // | |
| // CAUTION: This macro appends extra statements after a call, | |
| // thus it must never be used where only a single statement | |
| // is correct (e.g. an if statement branch w/o braces)! | |
| #define CHECK_OK_VALUE(x) ok); \ | |
| if (!*ok) return x; \ | |
| ((void)0 | |
| #define DUMMY ) // to make indentation work | |
| #undef DUMMY | |
| #define CHECK_OK CHECK_OK_VALUE(Expression::Default()) | |
| #define CHECK_OK_VOID CHECK_OK_VALUE(this->Void()) | |
| namespace { | |
| PreParserIdentifier GetSymbolHelper(Scanner* scanner) { | |
| // These symbols require slightly different treatement: | |
| // - regular keywords (async, await, etc.; treated in 1st switch.) | |
| // - 'contextual' keywords (and may contain escaped; treated in 2nd switch.) | |
| // - 'contextual' keywords, but may not be escaped (3rd switch). | |
| switch (scanner->current_token()) { | |
| case Token::AWAIT: | |
| return PreParserIdentifier::Await(); | |
| case Token::ASYNC: | |
| return PreParserIdentifier::Async(); | |
| case Token::PRIVATE_NAME: | |
| return PreParserIdentifier::PrivateName(); | |
| default: | |
| break; | |
| } | |
| switch (scanner->current_contextual_token()) { | |
| case Token::CONSTRUCTOR: | |
| return PreParserIdentifier::Constructor(); | |
| case Token::NAME: | |
| return PreParserIdentifier::Name(); | |
| default: | |
| break; | |
| } | |
| if (scanner->literal_contains_escapes()) { | |
| return PreParserIdentifier::Default(); | |
| } | |
| switch (scanner->current_contextual_token()) { | |
| case Token::EVAL: | |
| return PreParserIdentifier::Eval(); | |
| case Token::ARGUMENTS: | |
| return PreParserIdentifier::Arguments(); | |
| default: | |
| break; | |
| } | |
| return PreParserIdentifier::Default(); | |
| } | |
| } // unnamed namespace | |
| PreParserIdentifier PreParser::GetSymbol() const { | |
| PreParserIdentifier symbol = GetSymbolHelper(scanner()); | |
| if (track_unresolved_variables_) { | |
| const AstRawString* result = scanner()->CurrentSymbol(ast_value_factory()); | |
| DCHECK_NOT_NULL(result); | |
| symbol.string_ = result; | |
| } | |
| return symbol; | |
| } | |
| PreParser::PreParseResult PreParser::PreParseProgram() { | |
| DCHECK_NULL(scope_); | |
| DeclarationScope* scope = NewScriptScope(); | |
| #ifdef DEBUG | |
| scope->set_is_being_lazily_parsed(true); | |
| #endif | |
| // ModuleDeclarationInstantiation for Source Text Module Records creates a | |
| // new Module Environment Record whose outer lexical environment record is | |
| // the global scope. | |
| if (parsing_module_) scope = NewModuleScope(scope); | |
| FunctionState top_scope(&function_state_, &scope_, scope); | |
| original_scope_ = scope_; | |
| bool ok = true; | |
| int start_position = scanner()->peek_location().beg_pos; | |
| PreParserStatementList body; | |
| ParseStatementList(body, Token::EOS, &ok); | |
| original_scope_ = nullptr; | |
| if (stack_overflow()) return kPreParseStackOverflow; | |
| if (!ok) { | |
| ReportUnexpectedToken(scanner()->current_token()); | |
| } else if (is_strict(language_mode())) { | |
| CheckStrictOctalLiteral(start_position, scanner()->location().end_pos, &ok); | |
| } | |
| return kPreParseSuccess; | |
| } | |
| PreParser::PreParseResult PreParser::PreParseFunction( | |
| const AstRawString* function_name, FunctionKind kind, | |
| FunctionLiteral::FunctionType function_type, | |
| DeclarationScope* function_scope, bool is_inner_function, bool may_abort, | |
| int* use_counts, ProducedPreParsedScopeData** produced_preparsed_scope_data, | |
| int script_id) { | |
| DCHECK_EQ(FUNCTION_SCOPE, function_scope->scope_type()); | |
| use_counts_ = use_counts; | |
| DCHECK(!track_unresolved_variables_); | |
| track_unresolved_variables_ = is_inner_function; | |
| set_script_id(script_id); | |
| #ifdef DEBUG | |
| function_scope->set_is_being_lazily_parsed(true); | |
| #endif | |
| // Start collecting data for a new function which might contain skippable | |
| // functions. | |
| std::unique_ptr<ProducedPreParsedScopeData::DataGatheringScope> | |
| produced_preparsed_scope_data_scope; | |
| if (FLAG_preparser_scope_analysis && !IsArrowFunction(kind)) { | |
| track_unresolved_variables_ = true; | |
| produced_preparsed_scope_data_scope.reset( | |
| new ProducedPreParsedScopeData::DataGatheringScope(function_scope, | |
| this)); | |
| } | |
| // In the preparser, we use the function literal ids to count how many | |
| // FunctionLiterals were encountered. The PreParser doesn't actually persist | |
| // FunctionLiterals, so there IDs don't matter. | |
| ResetFunctionLiteralId(); | |
| // The caller passes the function_scope which is not yet inserted into the | |
| // scope stack. All scopes above the function_scope are ignored by the | |
| // PreParser. | |
| DCHECK_NULL(function_state_); | |
| DCHECK_NULL(scope_); | |
| FunctionState function_state(&function_state_, &scope_, function_scope); | |
| // This indirection is needed so that we can use the CHECK_OK macros. | |
| bool ok_holder = true; | |
| bool* ok = &ok_holder; | |
| PreParserFormalParameters formals(function_scope); | |
| DuplicateFinder duplicate_finder; | |
| std::unique_ptr<ExpressionClassifier> formals_classifier; | |
| // Parse non-arrow function parameters. For arrow functions, the parameters | |
| // have already been parsed. | |
| if (!IsArrowFunction(kind)) { | |
| formals_classifier.reset(new ExpressionClassifier(this, &duplicate_finder)); | |
| // We return kPreParseSuccess in failure cases too - errors are retrieved | |
| // separately by Parser::SkipLazyFunctionBody. | |
| ParseFormalParameterList(&formals, CHECK_OK_VALUE(kPreParseSuccess)); | |
| Expect(Token::RPAREN, CHECK_OK_VALUE(kPreParseSuccess)); | |
| int formals_end_position = scanner()->location().end_pos; | |
| CheckArityRestrictions( | |
| formals.arity, kind, formals.has_rest, function_scope->start_position(), | |
| formals_end_position, CHECK_OK_VALUE(kPreParseSuccess)); | |
| } | |
| Expect(Token::LBRACE, CHECK_OK_VALUE(kPreParseSuccess)); | |
| DeclarationScope* inner_scope = function_scope; | |
| LazyParsingResult result; | |
| if (!formals.is_simple) { | |
| inner_scope = NewVarblockScope(); | |
| inner_scope->set_start_position(scanner()->location().beg_pos); | |
| } | |
| { | |
| BlockState block_state(&scope_, inner_scope); | |
| result = ParseStatementListAndLogFunction(&formals, may_abort, ok); | |
| } | |
| if (!formals.is_simple) { | |
| BuildParameterInitializationBlock(formals, ok); | |
| if (is_sloppy(inner_scope->language_mode())) { | |
| inner_scope->HoistSloppyBlockFunctions(nullptr); | |
| } | |
| SetLanguageMode(function_scope, inner_scope->language_mode()); | |
| inner_scope->set_end_position(scanner()->peek_location().end_pos); | |
| inner_scope->FinalizeBlockScope(); | |
| } else { | |
| if (is_sloppy(function_scope->language_mode())) { | |
| function_scope->HoistSloppyBlockFunctions(nullptr); | |
| } | |
| } | |
| if (!IsArrowFunction(kind) && track_unresolved_variables_ && | |
| result == kLazyParsingComplete) { | |
| // Declare arguments after parsing the function since lexical 'arguments' | |
| // masks the arguments object. Declare arguments before declaring the | |
| // function var since the arguments object masks 'function arguments'. | |
| function_scope->DeclareArguments(ast_value_factory()); | |
| DeclareFunctionNameVar(function_name, function_type, function_scope); | |
| } | |
| use_counts_ = nullptr; | |
| track_unresolved_variables_ = false; | |
| if (result == kLazyParsingAborted) { | |
| return kPreParseAbort; | |
| } else if (stack_overflow()) { | |
| return kPreParseStackOverflow; | |
| } else if (!*ok) { | |
| DCHECK(pending_error_handler()->has_pending_error()); | |
| } else { | |
| DCHECK_EQ(Token::RBRACE, scanner()->peek()); | |
| if (!IsArrowFunction(kind)) { | |
| // Validate parameter names. We can do this only after parsing the | |
| // function, since the function can declare itself strict. | |
| const bool allow_duplicate_parameters = | |
| is_sloppy(function_scope->language_mode()) && formals.is_simple && | |
| !IsConciseMethod(kind); | |
| ValidateFormalParameters(function_scope->language_mode(), | |
| allow_duplicate_parameters, | |
| CHECK_OK_VALUE(kPreParseSuccess)); | |
| *produced_preparsed_scope_data = produced_preparsed_scope_data_; | |
| } | |
| if (is_strict(function_scope->language_mode())) { | |
| int end_pos = scanner()->location().end_pos; | |
| CheckStrictOctalLiteral(function_scope->start_position(), end_pos, ok); | |
| } | |
| } | |
| return kPreParseSuccess; | |
| } | |
| // Preparsing checks a JavaScript program and emits preparse-data that helps | |
| // a later parsing to be faster. | |
| // See preparser-data.h for the data. | |
| // The PreParser checks that the syntax follows the grammar for JavaScript, | |
| // and collects some information about the program along the way. | |
| // The grammar check is only performed in order to understand the program | |
| // sufficiently to deduce some information about it, that can be used | |
| // to speed up later parsing. Finding errors is not the goal of pre-parsing, | |
| // rather it is to speed up properly written and correct programs. | |
| // That means that contextual checks (like a label being declared where | |
| // it is used) are generally omitted. | |
| PreParser::Expression PreParser::ParseFunctionLiteral( | |
| Identifier function_name, Scanner::Location function_name_location, | |
| FunctionNameValidity function_name_validity, FunctionKind kind, | |
| int function_token_pos, FunctionLiteral::FunctionType function_type, | |
| LanguageMode language_mode, | |
| ZonePtrList<const AstRawString>* arguments_for_wrapped_function, bool* ok) { | |
| // Wrapped functions are not parsed in the preparser. | |
| DCHECK_NULL(arguments_for_wrapped_function); | |
| DCHECK_NE(FunctionLiteral::kWrapped, function_type); | |
| // Function :: | |
| // '(' FormalParameterList? ')' '{' FunctionBody '}' | |
| const RuntimeCallCounterId counters[2][2] = { | |
| {RuntimeCallCounterId::kPreParseBackgroundNoVariableResolution, | |
| RuntimeCallCounterId::kPreParseNoVariableResolution}, | |
| {RuntimeCallCounterId::kPreParseBackgroundWithVariableResolution, | |
| RuntimeCallCounterId::kPreParseWithVariableResolution}}; | |
| RuntimeCallTimerScope runtime_timer( | |
| runtime_call_stats_, | |
| counters[track_unresolved_variables_][parsing_on_main_thread_]); | |
| base::ElapsedTimer timer; | |
| if (V8_UNLIKELY(FLAG_log_function_events)) timer.Start(); | |
| DeclarationScope* function_scope = NewFunctionScope(kind); | |
| function_scope->SetLanguageMode(language_mode); | |
| // Start collecting data for a new function which might contain skippable | |
| // functions. | |
| std::unique_ptr<ProducedPreParsedScopeData::DataGatheringScope> | |
| produced_preparsed_scope_data_scope; | |
| if (!function_state_->next_function_is_likely_called() && | |
| produced_preparsed_scope_data_ != nullptr) { | |
| DCHECK(FLAG_preparser_scope_analysis); | |
| DCHECK(track_unresolved_variables_); | |
| produced_preparsed_scope_data_scope.reset( | |
| new ProducedPreParsedScopeData::DataGatheringScope(function_scope, | |
| this)); | |
| } | |
| FunctionState function_state(&function_state_, &scope_, function_scope); | |
| DuplicateFinder duplicate_finder; | |
| ExpressionClassifier formals_classifier(this, &duplicate_finder); | |
| int func_id = GetNextFunctionLiteralId(); | |
| Expect(Token::LPAREN, CHECK_OK); | |
| int start_position = scanner()->location().beg_pos; | |
| function_scope->set_start_position(start_position); | |
| PreParserFormalParameters formals(function_scope); | |
| ParseFormalParameterList(&formals, CHECK_OK); | |
| Expect(Token::RPAREN, CHECK_OK); | |
| int formals_end_position = scanner()->location().end_pos; | |
| CheckArityRestrictions(formals.arity, kind, formals.has_rest, start_position, | |
| formals_end_position, CHECK_OK); | |
| Expect(Token::LBRACE, CHECK_OK); | |
| // Parse function body. | |
| PreParserStatementList body; | |
| int pos = function_token_pos == kNoSourcePosition ? peek_position() | |
| : function_token_pos; | |
| ParseFunctionBody(body, function_name, pos, formals, kind, function_type, | |
| CHECK_OK); | |
| // Parsing the body may change the language mode in our scope. | |
| language_mode = function_scope->language_mode(); | |
| if (is_sloppy(language_mode)) { | |
| function_scope->HoistSloppyBlockFunctions(nullptr); | |
| } | |
| // Validate name and parameter names. We can do this only after parsing the | |
| // function, since the function can declare itself strict. | |
| CheckFunctionName(language_mode, function_name, function_name_validity, | |
| function_name_location, CHECK_OK); | |
| const bool allow_duplicate_parameters = | |
| is_sloppy(language_mode) && formals.is_simple && !IsConciseMethod(kind); | |
| ValidateFormalParameters(language_mode, allow_duplicate_parameters, CHECK_OK); | |
| int end_position = scanner()->location().end_pos; | |
| if (is_strict(language_mode)) { | |
| CheckStrictOctalLiteral(start_position, end_position, CHECK_OK); | |
| } | |
| if (produced_preparsed_scope_data_scope) { | |
| produced_preparsed_scope_data_scope->MarkFunctionAsSkippable( | |
| end_position, GetLastFunctionLiteralId() - func_id); | |
| } | |
| if (V8_UNLIKELY(FLAG_log_function_events)) { | |
| double ms = timer.Elapsed().InMillisecondsF(); | |
| const char* event_name = track_unresolved_variables_ | |
| ? "preparse-resolution" | |
| : "preparse-no-resolution"; | |
| // We might not always get a function name here. However, it can be easily | |
| // reconstructed from the script id and the byte range in the log processor. | |
| const char* name = ""; | |
| size_t name_byte_length = 0; | |
| const AstRawString* string = function_name.string_; | |
| if (string != nullptr) { | |
| name = reinterpret_cast<const char*>(string->raw_data()); | |
| name_byte_length = string->byte_length(); | |
| } | |
| logger_->FunctionEvent( | |
| event_name, script_id(), ms, function_scope->start_position(), | |
| function_scope->end_position(), name, name_byte_length); | |
| } | |
| return Expression::Default(); | |
| } | |
| PreParser::LazyParsingResult PreParser::ParseStatementListAndLogFunction( | |
| PreParserFormalParameters* formals, bool may_abort, bool* ok) { | |
| PreParserStatementList body; | |
| LazyParsingResult result = ParseStatementList( | |
| body, Token::RBRACE, may_abort, CHECK_OK_VALUE(kLazyParsingComplete)); | |
| if (result == kLazyParsingAborted) return result; | |
| // Position right after terminal '}'. | |
| DCHECK_EQ(Token::RBRACE, scanner()->peek()); | |
| int body_end = scanner()->peek_location().end_pos; | |
| DCHECK_EQ(this->scope()->is_function_scope(), formals->is_simple); | |
| log_.LogFunction(body_end, formals->num_parameters(), | |
| GetLastFunctionLiteralId()); | |
| return kLazyParsingComplete; | |
| } | |
| PreParserStatement PreParser::BuildParameterInitializationBlock( | |
| const PreParserFormalParameters& parameters, bool* ok) { | |
| DCHECK(!parameters.is_simple); | |
| DCHECK(scope()->is_function_scope()); | |
| if (FLAG_preparser_scope_analysis && | |
| scope()->AsDeclarationScope()->calls_sloppy_eval() && | |
| produced_preparsed_scope_data_ != nullptr) { | |
| // We cannot replicate the Scope structure constructed by the Parser, | |
| // because we've lost information whether each individual parameter was | |
| // simple or not. Give up trying to produce data to skip inner functions. | |
| if (produced_preparsed_scope_data_->parent() != nullptr) { | |
| // Lazy parsing started before the current function; the function which | |
| // cannot contain skippable functions is the parent function. (Its inner | |
| // functions cannot either; they are implicitly bailed out.) | |
| produced_preparsed_scope_data_->parent()->Bailout(); | |
| } else { | |
| // Lazy parsing started at the current function; it cannot contain | |
| // skippable functions. | |
| produced_preparsed_scope_data_->Bailout(); | |
| } | |
| } | |
| return PreParserStatement::Default(); | |
| } | |
| PreParserExpression PreParser::ExpressionFromIdentifier( | |
| const PreParserIdentifier& name, int start_position, InferName infer) { | |
| VariableProxy* proxy = nullptr; | |
| if (track_unresolved_variables_) { | |
| DCHECK_NOT_NULL(name.string_); | |
| proxy = scope()->NewUnresolved(factory()->ast_node_factory(), name.string_, | |
| start_position, NORMAL_VARIABLE); | |
| } | |
| return PreParserExpression::FromIdentifier(name, proxy, zone()); | |
| } | |
| void PreParser::DeclareAndInitializeVariables( | |
| PreParserStatement block, | |
| const DeclarationDescriptor* declaration_descriptor, | |
| const DeclarationParsingResult::Declaration* declaration, | |
| ZonePtrList<const AstRawString>* names, bool* ok) { | |
| if (declaration->pattern.variables_ != nullptr) { | |
| DCHECK(FLAG_lazy_inner_functions); | |
| DCHECK(track_unresolved_variables_); | |
| for (auto variable : *(declaration->pattern.variables_)) { | |
| declaration_descriptor->scope->RemoveUnresolved(variable); | |
| Variable* var = scope()->DeclareVariableName( | |
| variable->raw_name(), declaration_descriptor->mode); | |
| if (FLAG_preparser_scope_analysis) { | |
| MarkLoopVariableAsAssigned(declaration_descriptor->scope, var, | |
| declaration_descriptor->declaration_kind); | |
| // This is only necessary if there is an initializer, but we don't have | |
| // that information here. Consequently, the preparser sometimes says | |
| // maybe-assigned where the parser (correctly) says never-assigned. | |
| } | |
| if (names) { | |
| names->Add(variable->raw_name(), zone()); | |
| } | |
| } | |
| } | |
| } | |
| #undef CHECK_OK | |
| #undef CHECK_OK_CUSTOM | |
| } // namespace internal | |
| } // namespace v8 |