| // Copyright 2012 the V8 project authors. All rights reserved. |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following |
| // disclaimer in the documentation and/or other materials provided |
| // with the distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived |
| // from this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <memory> |
| |
| #include "src/init/v8.h" |
| |
| #include "src/api/api-inl.h" |
| #include "src/ast/ast-value-factory.h" |
| #include "src/ast/ast.h" |
| #include "src/base/enum-set.h" |
| #include "src/codegen/compiler.h" |
| #include "src/execution/execution.h" |
| #include "src/execution/isolate.h" |
| #include "src/flags/flags.h" |
| #include "src/objects/objects-inl.h" |
| #include "src/objects/objects.h" |
| #include "src/parsing/parse-info.h" |
| #include "src/parsing/parser.h" |
| #include "src/parsing/parsing.h" |
| #include "src/parsing/preparser.h" |
| #include "src/parsing/rewriter.h" |
| #include "src/parsing/scanner-character-streams.h" |
| #include "src/parsing/token.h" |
| #include "src/zone/zone-list-inl.h" // crbug.com/v8/8816 |
| |
| #include "test/cctest/cctest.h" |
| #include "test/cctest/scope-test-helper.h" |
| #include "test/cctest/unicode-helpers.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace test_parsing { |
| |
| namespace { |
| |
| int* global_use_counts = nullptr; |
| |
| void MockUseCounterCallback(v8::Isolate* isolate, |
| v8::Isolate::UseCounterFeature feature) { |
| ++global_use_counts[feature]; |
| } |
| |
| } // namespace |
| |
| // Helpers for parsing and checking that the result has no error, implemented as |
| // macros to report the correct test error location. |
| #define FAIL_WITH_PENDING_PARSER_ERROR(info, script, isolate) \ |
| do { \ |
| (info)->pending_error_handler()->PrepareErrors( \ |
| (isolate), (info)->ast_value_factory()); \ |
| (info)->pending_error_handler()->ReportErrors((isolate), (script)); \ |
| \ |
| i::Handle<i::JSObject> exception_handle( \ |
| i::JSObject::cast((isolate)->pending_exception()), (isolate)); \ |
| i::Handle<i::String> message_string = i::Handle<i::String>::cast( \ |
| i::JSReceiver::GetProperty((isolate), exception_handle, "message") \ |
| .ToHandleChecked()); \ |
| (isolate)->clear_pending_exception(); \ |
| \ |
| String source = String::cast((script)->source()); \ |
| \ |
| FATAL( \ |
| "Parser failed on:\n" \ |
| "\t%s\n" \ |
| "with error:\n" \ |
| "\t%s\n" \ |
| "However, we expected no error.", \ |
| source.ToCString().get(), message_string->ToCString().get()); \ |
| } while (false) |
| |
| #define CHECK_PARSE_PROGRAM(info, script, isolate) \ |
| do { \ |
| if (!i::parsing::ParseProgram((info), script, (isolate), \ |
| parsing::ReportStatisticsMode::kYes)) { \ |
| FAIL_WITH_PENDING_PARSER_ERROR((info), (script), (isolate)); \ |
| } \ |
| \ |
| CHECK(!(info)->pending_error_handler()->has_pending_error()); \ |
| CHECK_NOT_NULL((info)->literal()); \ |
| } while (false) |
| |
| #define CHECK_PARSE_FUNCTION(info, shared, isolate) \ |
| do { \ |
| if (!i::parsing::ParseFunction((info), (shared), (isolate), \ |
| parsing::ReportStatisticsMode::kYes)) { \ |
| FAIL_WITH_PENDING_PARSER_ERROR( \ |
| (info), handle(Script::cast((shared)->script()), (isolate)), \ |
| (isolate)); \ |
| } \ |
| \ |
| CHECK(!(info)->pending_error_handler()->has_pending_error()); \ |
| CHECK_NOT_NULL((info)->literal()); \ |
| } while (false) |
| |
| bool TokenIsAutoSemicolon(Token::Value token) { |
| switch (token) { |
| case Token::SEMICOLON: |
| case Token::EOS: |
| case Token::RBRACE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(AutoSemicolonToken) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsAutoSemicolon(token), Token::IsAutoSemicolon(token)); |
| } |
| } |
| |
| bool TokenIsAnyIdentifier(Token::Value token) { |
| switch (token) { |
| case Token::IDENTIFIER: |
| case Token::GET: |
| case Token::SET: |
| case Token::ASYNC: |
| case Token::AWAIT: |
| case Token::YIELD: |
| case Token::LET: |
| case Token::STATIC: |
| case Token::FUTURE_STRICT_RESERVED_WORD: |
| case Token::ESCAPED_STRICT_RESERVED_WORD: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(AnyIdentifierToken) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsAnyIdentifier(token), Token::IsAnyIdentifier(token)); |
| } |
| } |
| |
| bool TokenIsCallable(Token::Value token) { |
| switch (token) { |
| case Token::SUPER: |
| case Token::IDENTIFIER: |
| case Token::GET: |
| case Token::SET: |
| case Token::ASYNC: |
| case Token::AWAIT: |
| case Token::YIELD: |
| case Token::LET: |
| case Token::STATIC: |
| case Token::FUTURE_STRICT_RESERVED_WORD: |
| case Token::ESCAPED_STRICT_RESERVED_WORD: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(CallableToken) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsCallable(token), Token::IsCallable(token)); |
| } |
| } |
| |
| bool TokenIsValidIdentifier(Token::Value token, LanguageMode language_mode, |
| bool is_generator, bool disallow_await) { |
| switch (token) { |
| case Token::IDENTIFIER: |
| case Token::GET: |
| case Token::SET: |
| case Token::ASYNC: |
| return true; |
| case Token::YIELD: |
| return !is_generator && is_sloppy(language_mode); |
| case Token::AWAIT: |
| return !disallow_await; |
| case Token::LET: |
| case Token::STATIC: |
| case Token::FUTURE_STRICT_RESERVED_WORD: |
| case Token::ESCAPED_STRICT_RESERVED_WORD: |
| return is_sloppy(language_mode); |
| default: |
| return false; |
| } |
| UNREACHABLE(); |
| } |
| |
| TEST(IsValidIdentifierToken) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| for (size_t raw_language_mode = 0; raw_language_mode < LanguageModeSize; |
| raw_language_mode++) { |
| LanguageMode mode = static_cast<LanguageMode>(raw_language_mode); |
| for (int is_generator = 0; is_generator < 2; is_generator++) { |
| for (int disallow_await = 0; disallow_await < 2; disallow_await++) { |
| CHECK_EQ( |
| TokenIsValidIdentifier(token, mode, is_generator, disallow_await), |
| Token::IsValidIdentifier(token, mode, is_generator, |
| disallow_await)); |
| } |
| } |
| } |
| } |
| } |
| |
| bool TokenIsStrictReservedWord(Token::Value token) { |
| switch (token) { |
| case Token::LET: |
| case Token::YIELD: |
| case Token::STATIC: |
| case Token::FUTURE_STRICT_RESERVED_WORD: |
| case Token::ESCAPED_STRICT_RESERVED_WORD: |
| return true; |
| default: |
| return false; |
| } |
| UNREACHABLE(); |
| } |
| |
| TEST(IsStrictReservedWord) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsStrictReservedWord(token), |
| Token::IsStrictReservedWord(token)); |
| } |
| } |
| |
| bool TokenIsLiteral(Token::Value token) { |
| switch (token) { |
| case Token::NULL_LITERAL: |
| case Token::TRUE_LITERAL: |
| case Token::FALSE_LITERAL: |
| case Token::NUMBER: |
| case Token::SMI: |
| case Token::BIGINT: |
| case Token::STRING: |
| return true; |
| default: |
| return false; |
| } |
| UNREACHABLE(); |
| } |
| |
| TEST(IsLiteralToken) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsLiteral(token), Token::IsLiteral(token)); |
| } |
| } |
| |
| bool TokenIsAssignmentOp(Token::Value token) { |
| switch (token) { |
| case Token::INIT: |
| case Token::ASSIGN: |
| #define T(name, string, precedence) case Token::name: |
| BINARY_OP_TOKEN_LIST(T, EXPAND_BINOP_ASSIGN_TOKEN) |
| #undef T |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(AssignmentOp) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsAssignmentOp(token), Token::IsAssignmentOp(token)); |
| } |
| } |
| |
| bool TokenIsArrowOrAssignmentOp(Token::Value token) { |
| return token == Token::ARROW || TokenIsAssignmentOp(token); |
| } |
| |
| TEST(ArrowOrAssignmentOp) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsArrowOrAssignmentOp(token), |
| Token::IsArrowOrAssignmentOp(token)); |
| } |
| } |
| |
| bool TokenIsBinaryOp(Token::Value token) { |
| switch (token) { |
| case Token::COMMA: |
| #define T(name, string, precedence) case Token::name: |
| BINARY_OP_TOKEN_LIST(T, EXPAND_BINOP_TOKEN) |
| #undef T |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(BinaryOp) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsBinaryOp(token), Token::IsBinaryOp(token)); |
| } |
| } |
| |
| bool TokenIsCompareOp(Token::Value token) { |
| switch (token) { |
| case Token::EQ: |
| case Token::EQ_STRICT: |
| case Token::NE: |
| case Token::NE_STRICT: |
| case Token::LT: |
| case Token::GT: |
| case Token::LTE: |
| case Token::GTE: |
| case Token::INSTANCEOF: |
| case Token::IN: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(CompareOp) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsCompareOp(token), Token::IsCompareOp(token)); |
| } |
| } |
| |
| bool TokenIsOrderedRelationalCompareOp(Token::Value token) { |
| switch (token) { |
| case Token::LT: |
| case Token::GT: |
| case Token::LTE: |
| case Token::GTE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(IsOrderedRelationalCompareOp) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsOrderedRelationalCompareOp(token), |
| Token::IsOrderedRelationalCompareOp(token)); |
| } |
| } |
| |
| bool TokenIsEqualityOp(Token::Value token) { |
| switch (token) { |
| case Token::EQ: |
| case Token::EQ_STRICT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(IsEqualityOp) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsEqualityOp(token), Token::IsEqualityOp(token)); |
| } |
| } |
| |
| bool TokenIsBitOp(Token::Value token) { |
| switch (token) { |
| case Token::BIT_OR: |
| case Token::BIT_XOR: |
| case Token::BIT_AND: |
| case Token::SHL: |
| case Token::SAR: |
| case Token::SHR: |
| case Token::BIT_NOT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(IsBitOp) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsBitOp(token), Token::IsBitOp(token)); |
| } |
| } |
| |
| bool TokenIsUnaryOp(Token::Value token) { |
| switch (token) { |
| case Token::NOT: |
| case Token::BIT_NOT: |
| case Token::DELETE: |
| case Token::TYPEOF: |
| case Token::VOID: |
| case Token::ADD: |
| case Token::SUB: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(IsUnaryOp) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsUnaryOp(token), Token::IsUnaryOp(token)); |
| } |
| } |
| |
| bool TokenIsPropertyOrCall(Token::Value token) { |
| switch (token) { |
| case Token::TEMPLATE_SPAN: |
| case Token::TEMPLATE_TAIL: |
| case Token::PERIOD: |
| case Token::QUESTION_PERIOD: |
| case Token::LBRACK: |
| case Token::LPAREN: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(IsPropertyOrCall) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsPropertyOrCall(token), Token::IsPropertyOrCall(token)); |
| } |
| } |
| |
| bool TokenIsMember(Token::Value token) { |
| switch (token) { |
| case Token::TEMPLATE_SPAN: |
| case Token::TEMPLATE_TAIL: |
| case Token::PERIOD: |
| case Token::LBRACK: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool TokenIsTemplate(Token::Value token) { |
| switch (token) { |
| case Token::TEMPLATE_SPAN: |
| case Token::TEMPLATE_TAIL: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool TokenIsProperty(Token::Value token) { |
| switch (token) { |
| case Token::PERIOD: |
| case Token::LBRACK: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(IsMember) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsMember(token), Token::IsMember(token)); |
| } |
| } |
| |
| TEST(IsTemplate) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsTemplate(token), Token::IsTemplate(token)); |
| } |
| } |
| |
| TEST(IsProperty) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsProperty(token), Token::IsProperty(token)); |
| } |
| } |
| |
| bool TokenIsCountOp(Token::Value token) { |
| switch (token) { |
| case Token::INC: |
| case Token::DEC: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(IsCountOp) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsCountOp(token), Token::IsCountOp(token)); |
| } |
| } |
| |
| TEST(IsUnaryOrCountOp) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsUnaryOp(token) || TokenIsCountOp(token), |
| Token::IsUnaryOrCountOp(token)); |
| } |
| } |
| |
| bool TokenIsShiftOp(Token::Value token) { |
| switch (token) { |
| case Token::SHL: |
| case Token::SAR: |
| case Token::SHR: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| TEST(IsShiftOp) { |
| for (int i = 0; i < Token::NUM_TOKENS; i++) { |
| Token::Value token = static_cast<Token::Value>(i); |
| CHECK_EQ(TokenIsShiftOp(token), Token::IsShiftOp(token)); |
| } |
| } |
| |
| TEST(ScanKeywords) { |
| struct KeywordToken { |
| const char* keyword; |
| i::Token::Value token; |
| }; |
| |
| static const KeywordToken keywords[] = { |
| #define KEYWORD(t, s, d) { s, i::Token::t }, |
| TOKEN_LIST(IGNORE_TOKEN, KEYWORD) |
| #undef KEYWORD |
| {nullptr, i::Token::IDENTIFIER}}; |
| |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForTest(CcTest::i_isolate()); |
| KeywordToken key_token; |
| char buffer[32]; |
| for (int i = 0; (key_token = keywords[i]).keyword != nullptr; i++) { |
| const char* keyword = key_token.keyword; |
| size_t length = strlen(key_token.keyword); |
| CHECK(static_cast<int>(sizeof(buffer)) >= length); |
| { |
| auto stream = i::ScannerStream::ForTesting(keyword, length); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| CHECK_EQ(key_token.token, scanner.Next()); |
| CHECK_EQ(i::Token::EOS, scanner.Next()); |
| } |
| // Removing characters will make keyword matching fail. |
| { |
| auto stream = i::ScannerStream::ForTesting(keyword, length - 1); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| CHECK_EQ(i::Token::IDENTIFIER, scanner.Next()); |
| CHECK_EQ(i::Token::EOS, scanner.Next()); |
| } |
| // Adding characters will make keyword matching fail. |
| static const char chars_to_append[] = { 'z', '0', '_' }; |
| for (int j = 0; j < static_cast<int>(arraysize(chars_to_append)); ++j) { |
| i::MemMove(buffer, keyword, length); |
| buffer[length] = chars_to_append[j]; |
| auto stream = i::ScannerStream::ForTesting(buffer, length + 1); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| CHECK_EQ(i::Token::IDENTIFIER, scanner.Next()); |
| CHECK_EQ(i::Token::EOS, scanner.Next()); |
| } |
| // Replacing characters will make keyword matching fail. |
| { |
| i::MemMove(buffer, keyword, length); |
| buffer[length - 1] = '_'; |
| auto stream = i::ScannerStream::ForTesting(buffer, length); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| CHECK_EQ(i::Token::IDENTIFIER, scanner.Next()); |
| CHECK_EQ(i::Token::EOS, scanner.Next()); |
| } |
| } |
| } |
| |
| |
| TEST(ScanHTMLEndComments) { |
| v8::V8::Initialize(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| i::Isolate* i_isolate = CcTest::i_isolate(); |
| v8::HandleScope handles(isolate); |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForTest(i_isolate); |
| |
| // Regression test. See: |
| // http://code.google.com/p/chromium/issues/detail?id=53548 |
| // Tests that --> is correctly interpreted as comment-to-end-of-line if there |
| // is only whitespace before it on the line (with comments considered as |
| // whitespace, even a multiline-comment containing a newline). |
| // This was not the case if it occurred before the first real token |
| // in the input. |
| // clang-format off |
| const char* tests[] = { |
| // Before first real token. |
| "-->", |
| "--> is eol-comment", |
| "--> is eol-comment\nvar y = 37;\n", |
| "\n --> is eol-comment\nvar y = 37;\n", |
| "\n-->is eol-comment\nvar y = 37;\n", |
| "\n-->\nvar y = 37;\n", |
| "/* precomment */ --> is eol-comment\nvar y = 37;\n", |
| "/* precomment */-->eol-comment\nvar y = 37;\n", |
| "\n/* precomment */ --> is eol-comment\nvar y = 37;\n", |
| "\n/*precomment*/-->eol-comment\nvar y = 37;\n", |
| // After first real token. |
| "var x = 42;\n--> is eol-comment\nvar y = 37;\n", |
| "var x = 42;\n/* precomment */ --> is eol-comment\nvar y = 37;\n", |
| "x/* precomment\n */ --> is eol-comment\nvar y = 37;\n", |
| "var x = 42; /* precomment\n */ --> is eol-comment\nvar y = 37;\n", |
| "var x = 42;/*\n*/-->is eol-comment\nvar y = 37;\n", |
| // With multiple comments preceding HTMLEndComment |
| "/* MLC \n */ /* SLDC */ --> is eol-comment\nvar y = 37;\n", |
| "/* MLC \n */ /* SLDC1 */ /* SLDC2 */ --> is eol-comment\nvar y = 37;\n", |
| "/* MLC1 \n */ /* MLC2 \n */ --> is eol-comment\nvar y = 37;\n", |
| "/* SLDC */ /* MLC \n */ --> is eol-comment\nvar y = 37;\n", |
| "/* MLC1 \n */ /* SLDC1 */ /* MLC2 \n */ /* SLDC2 */ --> is eol-comment\n" |
| "var y = 37;\n", |
| nullptr |
| }; |
| |
| const char* fail_tests[] = { |
| "x --> is eol-comment\nvar y = 37;\n", |
| "\"\\n\" --> is eol-comment\nvar y = 37;\n", |
| "x/* precomment */ --> is eol-comment\nvar y = 37;\n", |
| "var x = 42; --> is eol-comment\nvar y = 37;\n", |
| nullptr |
| }; |
| // clang-format on |
| |
| // Parser/Scanner needs a stack limit. |
| i_isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - |
| 128 * 1024); |
| uintptr_t stack_limit = i_isolate->stack_guard()->real_climit(); |
| for (int i = 0; tests[i]; i++) { |
| const char* source = tests[i]; |
| auto stream = i::ScannerStream::ForTesting(source); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| i::Zone zone(i_isolate->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory( |
| &zone, i_isolate->ast_string_constants(), HashSeed(i_isolate)); |
| i::PendingCompilationErrorHandler pending_error_handler; |
| i::PreParser preparser(&zone, &scanner, stack_limit, &ast_value_factory, |
| &pending_error_handler, |
| i_isolate->counters()->runtime_call_stats(), |
| i_isolate->logger(), flags); |
| i::PreParser::PreParseResult result = preparser.PreParseProgram(); |
| CHECK_EQ(i::PreParser::kPreParseSuccess, result); |
| CHECK(!pending_error_handler.has_pending_error()); |
| } |
| |
| for (int i = 0; fail_tests[i]; i++) { |
| const char* source = fail_tests[i]; |
| auto stream = i::ScannerStream::ForTesting(source); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| i::Zone zone(i_isolate->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory( |
| &zone, i_isolate->ast_string_constants(), HashSeed(i_isolate)); |
| i::PendingCompilationErrorHandler pending_error_handler; |
| i::PreParser preparser(&zone, &scanner, stack_limit, &ast_value_factory, |
| &pending_error_handler, |
| i_isolate->counters()->runtime_call_stats(), |
| i_isolate->logger(), flags); |
| i::PreParser::PreParseResult result = preparser.PreParseProgram(); |
| // Even in the case of a syntax error, kPreParseSuccess is returned. |
| CHECK_EQ(i::PreParser::kPreParseSuccess, result); |
| CHECK(pending_error_handler.has_pending_error() || |
| pending_error_handler.has_error_unidentifiable_by_preparser()); |
| } |
| } |
| |
| TEST(ScanHtmlComments) { |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForTest(CcTest::i_isolate()); |
| |
| const char* src = "a <!-- b --> c"; |
| // Disallow HTML comments. |
| { |
| flags.set_is_module(true); |
| auto stream = i::ScannerStream::ForTesting(src); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| CHECK_EQ(i::Token::IDENTIFIER, scanner.Next()); |
| CHECK_EQ(i::Token::ILLEGAL, scanner.Next()); |
| } |
| |
| // Skip HTML comments: |
| { |
| flags.set_is_module(false); |
| auto stream = i::ScannerStream::ForTesting(src); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| CHECK_EQ(i::Token::IDENTIFIER, scanner.Next()); |
| CHECK_EQ(i::Token::EOS, scanner.Next()); |
| } |
| } |
| |
| class ScriptResource : public v8::String::ExternalOneByteStringResource { |
| public: |
| ScriptResource(const char* data, size_t length) |
| : data_(data), length_(length) { } |
| |
| const char* data() const override { return data_; } |
| size_t length() const override { return length_; } |
| |
| private: |
| const char* data_; |
| size_t length_; |
| }; |
| |
| |
| TEST(StandAlonePreParser) { |
| v8::V8::Initialize(); |
| i::Isolate* i_isolate = CcTest::i_isolate(); |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForTest(i_isolate); |
| flags.set_allow_natives_syntax(true); |
| |
| i_isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - |
| 128 * 1024); |
| |
| const char* programs[] = {"{label: 42}", |
| "var x = 42;", |
| "function foo(x, y) { return x + y; }", |
| "%ArgleBargle(glop);", |
| "var x = new new Function('this.x = 42');", |
| "var f = (x, y) => x + y;", |
| nullptr}; |
| |
| uintptr_t stack_limit = i_isolate->stack_guard()->real_climit(); |
| for (int i = 0; programs[i]; i++) { |
| auto stream = i::ScannerStream::ForTesting(programs[i]); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| |
| i::Zone zone(i_isolate->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory( |
| &zone, i_isolate->ast_string_constants(), HashSeed(i_isolate)); |
| i::PendingCompilationErrorHandler pending_error_handler; |
| i::PreParser preparser(&zone, &scanner, stack_limit, &ast_value_factory, |
| &pending_error_handler, |
| i_isolate->counters()->runtime_call_stats(), |
| i_isolate->logger(), flags); |
| i::PreParser::PreParseResult result = preparser.PreParseProgram(); |
| CHECK_EQ(i::PreParser::kPreParseSuccess, result); |
| CHECK(!pending_error_handler.has_pending_error()); |
| } |
| } |
| |
| |
| TEST(StandAlonePreParserNoNatives) { |
| v8::V8::Initialize(); |
| i::Isolate* isolate = CcTest::i_isolate(); |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForTest(isolate); |
| |
| isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - |
| 128 * 1024); |
| |
| const char* programs[] = {"%ArgleBargle(glop);", "var x = %_IsSmi(42);", |
| nullptr}; |
| |
| uintptr_t stack_limit = isolate->stack_guard()->real_climit(); |
| for (int i = 0; programs[i]; i++) { |
| auto stream = i::ScannerStream::ForTesting(programs[i]); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| |
| // Preparser defaults to disallowing natives syntax. |
| i::Zone zone(isolate->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory(&zone, isolate->ast_string_constants(), |
| HashSeed(isolate)); |
| i::PendingCompilationErrorHandler pending_error_handler; |
| i::PreParser preparser(&zone, &scanner, stack_limit, &ast_value_factory, |
| &pending_error_handler, |
| isolate->counters()->runtime_call_stats(), |
| isolate->logger(), flags); |
| i::PreParser::PreParseResult result = preparser.PreParseProgram(); |
| CHECK_EQ(i::PreParser::kPreParseSuccess, result); |
| CHECK(pending_error_handler.has_pending_error() || |
| pending_error_handler.has_error_unidentifiable_by_preparser()); |
| } |
| } |
| |
| |
| TEST(RegressChromium62639) { |
| v8::V8::Initialize(); |
| i::Isolate* isolate = CcTest::i_isolate(); |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForTest(isolate); |
| |
| isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - |
| 128 * 1024); |
| |
| const char* program = "var x = 'something';\n" |
| "escape: function() {}"; |
| // Fails parsing expecting an identifier after "function". |
| // Before fix, didn't check *ok after Expect(Token::Identifier, ok), |
| // and then used the invalid currently scanned literal. This always |
| // failed in debug mode, and sometimes crashed in release mode. |
| |
| auto stream = i::ScannerStream::ForTesting(program); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| i::Zone zone(isolate->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory(&zone, isolate->ast_string_constants(), |
| HashSeed(isolate)); |
| i::PendingCompilationErrorHandler pending_error_handler; |
| i::PreParser preparser(&zone, &scanner, isolate->stack_guard()->real_climit(), |
| &ast_value_factory, &pending_error_handler, |
| isolate->counters()->runtime_call_stats(), |
| isolate->logger(), flags); |
| i::PreParser::PreParseResult result = preparser.PreParseProgram(); |
| // Even in the case of a syntax error, kPreParseSuccess is returned. |
| CHECK_EQ(i::PreParser::kPreParseSuccess, result); |
| CHECK(pending_error_handler.has_pending_error() || |
| pending_error_handler.has_error_unidentifiable_by_preparser()); |
| } |
| |
| |
| TEST(PreParseOverflow) { |
| v8::V8::Initialize(); |
| i::Isolate* isolate = CcTest::i_isolate(); |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForTest(isolate); |
| |
| isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - |
| 128 * 1024); |
| |
| size_t kProgramSize = 1024 * 1024; |
| std::unique_ptr<char[]> program(i::NewArray<char>(kProgramSize + 1)); |
| memset(program.get(), '(', kProgramSize); |
| program[kProgramSize] = '\0'; |
| |
| uintptr_t stack_limit = isolate->stack_guard()->real_climit(); |
| |
| auto stream = i::ScannerStream::ForTesting(program.get(), kProgramSize); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| |
| i::Zone zone(isolate->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory(&zone, isolate->ast_string_constants(), |
| HashSeed(isolate)); |
| i::PendingCompilationErrorHandler pending_error_handler; |
| i::PreParser preparser( |
| &zone, &scanner, stack_limit, &ast_value_factory, &pending_error_handler, |
| isolate->counters()->runtime_call_stats(), isolate->logger(), flags); |
| i::PreParser::PreParseResult result = preparser.PreParseProgram(); |
| CHECK_EQ(i::PreParser::kPreParseStackOverflow, result); |
| } |
| |
| void TestStreamScanner(i::Utf16CharacterStream* stream, |
| i::Token::Value* expected_tokens, |
| int skip_pos = 0, // Zero means not skipping. |
| int skip_to = 0) { |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForTest(CcTest::i_isolate()); |
| |
| i::Scanner scanner(stream, flags); |
| scanner.Initialize(); |
| |
| int i = 0; |
| do { |
| i::Token::Value expected = expected_tokens[i]; |
| i::Token::Value actual = scanner.Next(); |
| CHECK_EQ(i::Token::String(expected), i::Token::String(actual)); |
| if (scanner.location().end_pos == skip_pos) { |
| scanner.SeekForward(skip_to); |
| } |
| i++; |
| } while (expected_tokens[i] != i::Token::ILLEGAL); |
| } |
| |
| |
| TEST(StreamScanner) { |
| v8::V8::Initialize(); |
| const char* str1 = "{ foo get for : */ <- \n\n /*foo*/ bib"; |
| std::unique_ptr<i::Utf16CharacterStream> stream1( |
| i::ScannerStream::ForTesting(str1)); |
| i::Token::Value expectations1[] = { |
| i::Token::LBRACE, i::Token::IDENTIFIER, i::Token::GET, i::Token::FOR, |
| i::Token::COLON, i::Token::MUL, i::Token::DIV, i::Token::LT, |
| i::Token::SUB, i::Token::IDENTIFIER, i::Token::EOS, i::Token::ILLEGAL}; |
| TestStreamScanner(stream1.get(), expectations1, 0, 0); |
| |
| const char* str2 = "case default const {THIS\nPART\nSKIPPED} do"; |
| std::unique_ptr<i::Utf16CharacterStream> stream2( |
| i::ScannerStream::ForTesting(str2)); |
| i::Token::Value expectations2[] = { |
| i::Token::CASE, |
| i::Token::DEFAULT, |
| i::Token::CONST, |
| i::Token::LBRACE, |
| // Skipped part here |
| i::Token::RBRACE, |
| i::Token::DO, |
| i::Token::EOS, |
| i::Token::ILLEGAL |
| }; |
| CHECK_EQ('{', str2[19]); |
| CHECK_EQ('}', str2[37]); |
| TestStreamScanner(stream2.get(), expectations2, 20, 37); |
| |
| const char* str3 = "{}}}}"; |
| i::Token::Value expectations3[] = { |
| i::Token::LBRACE, |
| i::Token::RBRACE, |
| i::Token::RBRACE, |
| i::Token::RBRACE, |
| i::Token::RBRACE, |
| i::Token::EOS, |
| i::Token::ILLEGAL |
| }; |
| // Skip zero-four RBRACEs. |
| for (int i = 0; i <= 4; i++) { |
| expectations3[6 - i] = i::Token::ILLEGAL; |
| expectations3[5 - i] = i::Token::EOS; |
| std::unique_ptr<i::Utf16CharacterStream> stream3( |
| i::ScannerStream::ForTesting(str3)); |
| TestStreamScanner(stream3.get(), expectations3, 1, 1 + i); |
| } |
| } |
| |
| void TestScanRegExp(const char* re_source, const char* expected) { |
| auto stream = i::ScannerStream::ForTesting(re_source); |
| i::HandleScope scope(CcTest::i_isolate()); |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForTest(CcTest::i_isolate()); |
| i::Scanner scanner(stream.get(), flags); |
| scanner.Initialize(); |
| |
| i::Token::Value start = scanner.peek(); |
| CHECK(start == i::Token::DIV || start == i::Token::ASSIGN_DIV); |
| CHECK(scanner.ScanRegExpPattern()); |
| scanner.Next(); // Current token is now the regexp literal. |
| i::Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory( |
| &zone, CcTest::i_isolate()->ast_string_constants(), |
| HashSeed(CcTest::i_isolate())); |
| const i::AstRawString* current_symbol = |
| scanner.CurrentSymbol(&ast_value_factory); |
| ast_value_factory.Internalize(CcTest::i_isolate()); |
| i::Handle<i::String> val = current_symbol->string(); |
| i::DisallowHeapAllocation no_alloc; |
| i::String::FlatContent content = val->GetFlatContent(no_alloc); |
| CHECK(content.IsOneByte()); |
| i::Vector<const uint8_t> actual = content.ToOneByteVector(); |
| for (int i = 0; i < actual.length(); i++) { |
| CHECK_NE('\0', expected[i]); |
| CHECK_EQ(expected[i], actual[i]); |
| } |
| } |
| |
| |
| TEST(RegExpScanning) { |
| v8::V8::Initialize(); |
| |
| // RegExp token with added garbage at the end. The scanner should only |
| // scan the RegExp until the terminating slash just before "flipperwald". |
| TestScanRegExp("/b/flipperwald", "b"); |
| // Incomplete escape sequences doesn't hide the terminating slash. |
| TestScanRegExp("/\\x/flipperwald", "\\x"); |
| TestScanRegExp("/\\u/flipperwald", "\\u"); |
| TestScanRegExp("/\\u1/flipperwald", "\\u1"); |
| TestScanRegExp("/\\u12/flipperwald", "\\u12"); |
| TestScanRegExp("/\\u123/flipperwald", "\\u123"); |
| TestScanRegExp("/\\c/flipperwald", "\\c"); |
| TestScanRegExp("/\\c//flipperwald", "\\c"); |
| // Slashes inside character classes are not terminating. |
| TestScanRegExp("/[/]/flipperwald", "[/]"); |
| TestScanRegExp("/[\\s-/]/flipperwald", "[\\s-/]"); |
| // Incomplete escape sequences inside a character class doesn't hide |
| // the end of the character class. |
| TestScanRegExp("/[\\c/]/flipperwald", "[\\c/]"); |
| TestScanRegExp("/[\\c]/flipperwald", "[\\c]"); |
| TestScanRegExp("/[\\x]/flipperwald", "[\\x]"); |
| TestScanRegExp("/[\\x1]/flipperwald", "[\\x1]"); |
| TestScanRegExp("/[\\u]/flipperwald", "[\\u]"); |
| TestScanRegExp("/[\\u1]/flipperwald", "[\\u1]"); |
| TestScanRegExp("/[\\u12]/flipperwald", "[\\u12]"); |
| TestScanRegExp("/[\\u123]/flipperwald", "[\\u123]"); |
| // Escaped ']'s wont end the character class. |
| TestScanRegExp("/[\\]/]/flipperwald", "[\\]/]"); |
| // Escaped slashes are not terminating. |
| TestScanRegExp("/\\//flipperwald", "\\/"); |
| // Starting with '=' works too. |
| TestScanRegExp("/=/", "="); |
| TestScanRegExp("/=?/", "=?"); |
| } |
| |
| TEST(ScopeUsesArgumentsSuperThis) { |
| static const struct { |
| const char* prefix; |
| const char* suffix; |
| } surroundings[] = { |
| { "function f() {", "}" }, |
| { "var f = () => {", "};" }, |
| { "class C { constructor() {", "} }" }, |
| }; |
| |
| enum Expected { |
| NONE = 0, |
| ARGUMENTS = 1, |
| SUPER_PROPERTY = 1 << 1, |
| THIS = 1 << 2, |
| EVAL = 1 << 4 |
| }; |
| |
| // clang-format off |
| static const struct { |
| const char* body; |
| int expected; |
| } source_data[] = { |
| {"", NONE}, |
| {"return this", THIS}, |
| {"return arguments", ARGUMENTS}, |
| {"return super.x", SUPER_PROPERTY}, |
| {"return arguments[0]", ARGUMENTS}, |
| {"return this + arguments[0]", ARGUMENTS | THIS}, |
| {"return this + arguments[0] + super.x", |
| ARGUMENTS | SUPER_PROPERTY | THIS}, |
| {"return x => this + x", THIS}, |
| {"return x => super.f() + x", SUPER_PROPERTY}, |
| {"this.foo = 42;", THIS}, |
| {"this.foo();", THIS}, |
| {"if (foo()) { this.f() }", THIS}, |
| {"if (foo()) { super.f() }", SUPER_PROPERTY}, |
| {"if (arguments.length) { this.f() }", ARGUMENTS | THIS}, |
| {"while (true) { this.f() }", THIS}, |
| {"while (true) { super.f() }", SUPER_PROPERTY}, |
| {"if (true) { while (true) this.foo(arguments) }", ARGUMENTS | THIS}, |
| // Multiple nesting levels must work as well. |
| {"while (true) { while (true) { while (true) return this } }", THIS}, |
| {"while (true) { while (true) { while (true) return super.f() } }", |
| SUPER_PROPERTY}, |
| {"if (1) { return () => { while (true) new this() } }", THIS}, |
| {"return function (x) { return this + x }", NONE}, |
| {"return { m(x) { return super.m() + x } }", NONE}, |
| {"var x = function () { this.foo = 42 };", NONE}, |
| {"var x = { m() { super.foo = 42 } };", NONE}, |
| {"if (1) { return function () { while (true) new this() } }", NONE}, |
| {"if (1) { return { m() { while (true) super.m() } } }", NONE}, |
| {"return function (x) { return () => this }", NONE}, |
| {"return { m(x) { return () => super.m() } }", NONE}, |
| // Flags must be correctly set when using block scoping. |
| {"\"use strict\"; while (true) { let x; this, arguments; }", |
| THIS}, |
| {"\"use strict\"; while (true) { let x; this, super.f(), arguments; }", |
| SUPER_PROPERTY | THIS}, |
| {"\"use strict\"; if (foo()) { let x; this.f() }", THIS}, |
| {"\"use strict\"; if (foo()) { let x; super.f() }", SUPER_PROPERTY}, |
| {"\"use strict\"; if (1) {" |
| " let x; return { m() { return this + super.m() + arguments } }" |
| "}", |
| NONE}, |
| {"eval(42)", EVAL}, |
| {"if (1) { eval(42) }", EVAL}, |
| {"eval('super.x')", EVAL}, |
| {"eval('this.x')", EVAL}, |
| {"eval('arguments')", EVAL}, |
| }; |
| // clang-format on |
| |
| i::Isolate* isolate = CcTest::i_isolate(); |
| i::Factory* factory = isolate->factory(); |
| |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - |
| 128 * 1024); |
| |
| for (unsigned j = 0; j < arraysize(surroundings); ++j) { |
| for (unsigned i = 0; i < arraysize(source_data); ++i) { |
| // Super property is only allowed in constructor and method. |
| if (((source_data[i].expected & SUPER_PROPERTY) || |
| (source_data[i].expected == NONE)) && j != 2) { |
| continue; |
| } |
| int kProgramByteSize = static_cast<int>(strlen(surroundings[j].prefix) + |
| strlen(surroundings[j].suffix) + |
| strlen(source_data[i].body)); |
| i::ScopedVector<char> program(kProgramByteSize + 1); |
| i::SNPrintF(program, "%s%s%s", surroundings[j].prefix, |
| source_data[i].body, surroundings[j].suffix); |
| i::Handle<i::String> source = |
| factory->NewStringFromUtf8(i::CStrVector(program.begin())) |
| .ToHandleChecked(); |
| i::Handle<i::Script> script = factory->NewScript(source); |
| i::UnoptimizedCompileState compile_state(isolate); |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForScriptCompile(isolate, *script); |
| // The information we're checking is only produced when eager parsing. |
| flags.set_allow_lazy_parsing(false); |
| i::ParseInfo info(isolate, flags, &compile_state); |
| CHECK_PARSE_PROGRAM(&info, script, isolate); |
| i::DeclarationScope::AllocateScopeInfos(&info, isolate); |
| CHECK_NOT_NULL(info.literal()); |
| |
| i::DeclarationScope* script_scope = info.literal()->scope(); |
| CHECK(script_scope->is_script_scope()); |
| |
| i::Scope* scope = script_scope->inner_scope(); |
| DCHECK_NOT_NULL(scope); |
| DCHECK_NULL(scope->sibling()); |
| // Adjust for constructor scope. |
| if (j == 2) { |
| scope = scope->inner_scope(); |
| DCHECK_NOT_NULL(scope); |
| DCHECK_NULL(scope->sibling()); |
| } |
| // Arrows themselves never get an arguments object. |
| if ((source_data[i].expected & ARGUMENTS) != 0 && |
| !scope->AsDeclarationScope()->is_arrow_scope()) { |
| CHECK_NOT_NULL(scope->AsDeclarationScope()->arguments()); |
| } |
| if (IsClassConstructor(scope->AsDeclarationScope()->function_kind())) { |
| CHECK_EQ((source_data[i].expected & SUPER_PROPERTY) != 0 || |
| (source_data[i].expected & EVAL) != 0, |
| scope->AsDeclarationScope()->NeedsHomeObject()); |
| } else { |
| CHECK_EQ((source_data[i].expected & SUPER_PROPERTY) != 0, |
| scope->AsDeclarationScope()->NeedsHomeObject()); |
| } |
| if ((source_data[i].expected & THIS) != 0) { |
| // Currently the is_used() flag is conservative; all variables in a |
| // script scope are marked as used. |
| CHECK(scope->GetReceiverScope()->receiver()->is_used()); |
| } |
| if (is_sloppy(scope->language_mode())) { |
| CHECK_EQ((source_data[i].expected & EVAL) != 0, |
| scope->AsDeclarationScope()->sloppy_eval_can_extend_vars()); |
| } |
| } |
| } |
| } |
| |
| static void CheckParsesToNumber(const char* source) { |
| v8::V8::Initialize(); |
| HandleAndZoneScope handles; |
| |
| i::Isolate* isolate = CcTest::i_isolate(); |
| i::Factory* factory = isolate->factory(); |
| |
| std::string full_source = "function f() { return "; |
| full_source += source; |
| full_source += "; }"; |
| |
| i::Handle<i::String> source_code = |
| factory->NewStringFromUtf8(i::CStrVector(full_source.c_str())) |
| .ToHandleChecked(); |
| |
| i::Handle<i::Script> script = factory->NewScript(source_code); |
| |
| i::UnoptimizedCompileState compile_state(isolate); |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForScriptCompile(isolate, *script); |
| flags.set_allow_lazy_parsing(false); |
| flags.set_is_toplevel(true); |
| i::ParseInfo info(isolate, flags, &compile_state); |
| |
| CHECK_PARSE_PROGRAM(&info, script, isolate); |
| |
| CHECK_EQ(1, info.scope()->declarations()->LengthForTest()); |
| i::Declaration* decl = info.scope()->declarations()->AtForTest(0); |
| i::FunctionLiteral* fun = decl->AsFunctionDeclaration()->fun(); |
| CHECK_EQ(fun->body()->length(), 1); |
| CHECK(fun->body()->at(0)->IsReturnStatement()); |
| i::ReturnStatement* ret = fun->body()->at(0)->AsReturnStatement(); |
| i::Literal* lit = ret->expression()->AsLiteral(); |
| CHECK(lit->IsNumberLiteral()); |
| } |
| |
| |
| TEST(ParseNumbers) { |
| CheckParsesToNumber("1."); |
| CheckParsesToNumber("1.34"); |
| CheckParsesToNumber("134"); |
| CheckParsesToNumber("134e44"); |
| CheckParsesToNumber("134.e44"); |
| CheckParsesToNumber("134.44e44"); |
| CheckParsesToNumber(".44"); |
| |
| CheckParsesToNumber("-1."); |
| CheckParsesToNumber("-1.0"); |
| CheckParsesToNumber("-1.34"); |
| CheckParsesToNumber("-134"); |
| CheckParsesToNumber("-134e44"); |
| CheckParsesToNumber("-134.e44"); |
| CheckParsesToNumber("-134.44e44"); |
| CheckParsesToNumber("-.44"); |
| } |
| |
| |
| TEST(ScopePositions) { |
| // Test the parser for correctly setting the start and end positions |
| // of a scope. We check the scope positions of exactly one scope |
| // nested in the global scope of a program. 'inner source' is the |
| // source code that determines the part of the source belonging |
| // to the nested scope. 'outer_prefix' and 'outer_suffix' are |
| // parts of the source that belong to the global scope. |
| struct SourceData { |
| const char* outer_prefix; |
| const char* inner_source; |
| const char* outer_suffix; |
| i::ScopeType scope_type; |
| i::LanguageMode language_mode; |
| }; |
| |
| const SourceData source_data[] = { |
| {" with ({}) ", "{ block; }", " more;", i::WITH_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {" with ({}) ", "{ block; }", "; more;", i::WITH_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {" with ({}) ", |
| "{\n" |
| " block;\n" |
| " }", |
| "\n" |
| " more;", |
| i::WITH_SCOPE, i::LanguageMode::kSloppy}, |
| {" with ({}) ", "statement;", " more;", i::WITH_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {" with ({}) ", "statement", |
| "\n" |
| " more;", |
| i::WITH_SCOPE, i::LanguageMode::kSloppy}, |
| {" with ({})\n" |
| " ", |
| "statement;", |
| "\n" |
| " more;", |
| i::WITH_SCOPE, i::LanguageMode::kSloppy}, |
| {" try {} catch ", "(e) { block; }", " more;", i::CATCH_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {" try {} catch ", "(e) { block; }", "; more;", i::CATCH_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {" try {} catch ", |
| "(e) {\n" |
| " block;\n" |
| " }", |
| "\n" |
| " more;", |
| i::CATCH_SCOPE, i::LanguageMode::kSloppy}, |
| {" try {} catch ", "(e) { block; }", " finally { block; } more;", |
| i::CATCH_SCOPE, i::LanguageMode::kSloppy}, |
| {" start;\n" |
| " ", |
| "{ let block; }", " more;", i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| {" start;\n" |
| " ", |
| "{ let block; }", "; more;", i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| {" start;\n" |
| " ", |
| "{\n" |
| " let block;\n" |
| " }", |
| "\n" |
| " more;", |
| i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| {" start;\n" |
| " function fun", |
| "(a,b) { infunction; }", " more;", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {" start;\n" |
| " function fun", |
| "(a,b) {\n" |
| " infunction;\n" |
| " }", |
| "\n" |
| " more;", |
| i::FUNCTION_SCOPE, i::LanguageMode::kSloppy}, |
| {" start;\n", "(a,b) => a + b", "; more;", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {" start;\n", "(a,b) => { return a+b; }", "\nmore;", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {" start;\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {" for ", "(let x = 1 ; x < 10; ++ x) { block; }", " more;", |
| i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| {" for ", "(let x = 1 ; x < 10; ++ x) { block; }", "; more;", |
| i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| {" for ", |
| "(let x = 1 ; x < 10; ++ x) {\n" |
| " block;\n" |
| " }", |
| "\n" |
| " more;", |
| i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| {" for ", "(let x = 1 ; x < 10; ++ x) statement;", " more;", |
| i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| {" for ", "(let x = 1 ; x < 10; ++ x) statement", |
| "\n" |
| " more;", |
| i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| {" for ", |
| "(let x = 1 ; x < 10; ++ x)\n" |
| " statement;", |
| "\n" |
| " more;", |
| i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| {" for ", "(let x in {}) { block; }", " more;", i::BLOCK_SCOPE, |
| i::LanguageMode::kStrict}, |
| {" for ", "(let x in {}) { block; }", "; more;", i::BLOCK_SCOPE, |
| i::LanguageMode::kStrict}, |
| {" for ", |
| "(let x in {}) {\n" |
| " block;\n" |
| " }", |
| "\n" |
| " more;", |
| i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| {" for ", "(let x in {}) statement;", " more;", i::BLOCK_SCOPE, |
| i::LanguageMode::kStrict}, |
| {" for ", "(let x in {}) statement", |
| "\n" |
| " more;", |
| i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| {" for ", |
| "(let x in {})\n" |
| " statement;", |
| "\n" |
| " more;", |
| i::BLOCK_SCOPE, i::LanguageMode::kStrict}, |
| // Check that 6-byte and 4-byte encodings of UTF-8 strings do not throw |
| // the preparser off in terms of byte offsets. |
| // 2 surrogates, encode a character that doesn't need a surrogate. |
| {" 'foo\xED\xA0\x81\xED\xB0\x89';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // 4-byte encoding. |
| {" 'foo\xF0\x90\x90\x8A';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // 3-byte encoding of \u0FFF. |
| {" 'foo\xE0\xBF\xBF';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // 3-byte surrogate, followed by broken 2-byte surrogate w/ impossible 2nd |
| // byte and last byte missing. |
| {" 'foo\xED\xA0\x81\xED\x89';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Broken 3-byte encoding of \u0FFF with missing last byte. |
| {" 'foo\xE0\xBF';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Broken 3-byte encoding of \u0FFF with missing 2 last bytes. |
| {" 'foo\xE0';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Broken 3-byte encoding of \u00FF should be a 2-byte encoding. |
| {" 'foo\xE0\x83\xBF';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Broken 3-byte encoding of \u007F should be a 2-byte encoding. |
| {" 'foo\xE0\x81\xBF';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Unpaired lead surrogate. |
| {" 'foo\xED\xA0\x81';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Unpaired lead surrogate where the following code point is a 3-byte |
| // sequence. |
| {" 'foo\xED\xA0\x81\xE0\xBF\xBF';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Unpaired lead surrogate where the following code point is a 4-byte |
| // encoding of a trail surrogate. |
| {" 'foo\xED\xA0\x81\xF0\x8D\xB0\x89';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Unpaired trail surrogate. |
| {" 'foo\xED\xB0\x89';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // 2-byte encoding of \u00FF. |
| {" 'foo\xC3\xBF';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Broken 2-byte encoding of \u00FF with missing last byte. |
| {" 'foo\xC3';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Broken 2-byte encoding of \u007F should be a 1-byte encoding. |
| {" 'foo\xC1\xBF';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Illegal 5-byte encoding. |
| {" 'foo\xF8\xBF\xBF\xBF\xBF';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Illegal 6-byte encoding. |
| {" 'foo\xFC\xBF\xBF\xBF\xBF\xBF';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Illegal 0xFE byte |
| {" 'foo\xFE\xBF\xBF\xBF\xBF\xBF\xBF';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| // Illegal 0xFF byte |
| {" 'foo\xFF\xBF\xBF\xBF\xBF\xBF\xBF\xBF';\n" |
| " (function fun", |
| "(a,b) { infunction; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {" 'foo';\n" |
| " (function fun", |
| "(a,b) { 'bar\xED\xA0\x81\xED\xB0\x8B'; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {" 'foo';\n" |
| " (function fun", |
| "(a,b) { 'bar\xF0\x90\x90\x8C'; }", ")();", i::FUNCTION_SCOPE, |
| i::LanguageMode::kSloppy}, |
| {nullptr, nullptr, nullptr, i::EVAL_SCOPE, i::LanguageMode::kSloppy}}; |
| |
| i::Isolate* isolate = CcTest::i_isolate(); |
| i::Factory* factory = isolate->factory(); |
| |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - |
| 128 * 1024); |
| |
| for (int i = 0; source_data[i].outer_prefix; i++) { |
| int kPrefixLen = Utf8LengthHelper(source_data[i].outer_prefix); |
| int kInnerLen = Utf8LengthHelper(source_data[i].inner_source); |
| int kSuffixLen = Utf8LengthHelper(source_data[i].outer_suffix); |
| int kPrefixByteLen = static_cast<int>(strlen(source_data[i].outer_prefix)); |
| int kInnerByteLen = static_cast<int>(strlen(source_data[i].inner_source)); |
| int kSuffixByteLen = static_cast<int>(strlen(source_data[i].outer_suffix)); |
| int kProgramSize = kPrefixLen + kInnerLen + kSuffixLen; |
| int kProgramByteSize = kPrefixByteLen + kInnerByteLen + kSuffixByteLen; |
| i::ScopedVector<char> program(kProgramByteSize + 1); |
| i::SNPrintF(program, "%s%s%s", |
| source_data[i].outer_prefix, |
| source_data[i].inner_source, |
| source_data[i].outer_suffix); |
| |
| // Parse program source. |
| i::Handle<i::String> source = |
| factory->NewStringFromUtf8(i::CStrVector(program.begin())) |
| .ToHandleChecked(); |
| CHECK_EQ(source->length(), kProgramSize); |
| i::Handle<i::Script> script = factory->NewScript(source); |
| |
| i::UnoptimizedCompileState compile_state(isolate); |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForScriptCompile(isolate, *script); |
| flags.set_outer_language_mode(source_data[i].language_mode); |
| i::ParseInfo info(isolate, flags, &compile_state); |
| CHECK_PARSE_PROGRAM(&info, script, isolate); |
| |
| // Check scope types and positions. |
| i::Scope* scope = info.literal()->scope(); |
| CHECK(scope->is_script_scope()); |
| CHECK_EQ(0, scope->start_position()); |
| CHECK_EQ(scope->end_position(), kProgramSize); |
| |
| i::Scope* inner_scope = scope->inner_scope(); |
| DCHECK_NOT_NULL(inner_scope); |
| DCHECK_NULL(inner_scope->sibling()); |
| CHECK_EQ(inner_scope->scope_type(), source_data[i].scope_type); |
| CHECK_EQ(inner_scope->start_position(), kPrefixLen); |
| // The end position of a token is one position after the last |
| // character belonging to that token. |
| CHECK_EQ(inner_scope->end_position(), kPrefixLen + kInnerLen); |
| } |
| } |
| |
| |
| TEST(DiscardFunctionBody) { |
| // Test that inner function bodies are discarded if possible. |
| // See comments in ParseFunctionLiteral in parser.cc. |
| const char* discard_sources[] = { |
| "(function f() { function g() { var a; } })();", |
| "(function f() { function g() { { function h() { } } } })();", |
| /* TODO(conradw): In future it may be possible to apply this optimisation |
| * to these productions. |
| "(function f() { 0, function g() { var a; } })();", |
| "(function f() { 0, { g() { var a; } } })();", |
| "(function f() { 0, class c { g() { var a; } } })();", */ |
| nullptr}; |
| |
| i::Isolate* isolate = CcTest::i_isolate(); |
| i::Factory* factory = isolate->factory(); |
| v8::HandleScope handles(CcTest::isolate()); |
| i::FunctionLiteral* function; |
| |
| for (int i = 0; discard_sources[i]; i++) { |
| const char* source = discard_sources[i]; |
| i::Handle<i::String> source_code = |
| factory->NewStringFromUtf8(i::CStrVector(source)).ToHandleChecked(); |
| i::Handle<i::Script> script = factory->NewScript(source_code); |
| i::UnoptimizedCompileState compile_state(isolate); |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForScriptCompile(isolate, *script); |
| i::ParseInfo info(isolate, flags, &compile_state); |
| CHECK_PARSE_PROGRAM(&info, script, isolate); |
| function = info.literal(); |
| CHECK_NOT_NULL(function); |
| // The rewriter will rewrite this to |
| // .result = (function f(){...})(); |
| // return .result; |
| // so extract the function from there. |
| CHECK_EQ(2, function->body()->length()); |
| i::FunctionLiteral* inner = function->body() |
| ->first() |
| ->AsExpressionStatement() |
| ->expression() |
| ->AsAssignment() |
| ->value() |
| ->AsCall() |
| ->expression() |
| ->AsFunctionLiteral(); |
| i::Scope* inner_scope = inner->scope(); |
| i::FunctionLiteral* fun = nullptr; |
| if (!inner_scope->declarations()->is_empty()) { |
| fun = inner_scope->declarations() |
| ->AtForTest(0) |
| ->AsFunctionDeclaration() |
| ->fun(); |
| } else { |
| // TODO(conradw): This path won't be hit until the other test cases can be |
| // uncommented. |
| UNREACHABLE(); |
| CHECK(inner->ShouldEagerCompile()); |
| CHECK_GE(2, inner->body()->length()); |
| i::Expression* exp = inner->body()->at(1)->AsExpressionStatement()-> |
| expression()->AsBinaryOperation()->right(); |
| if (exp->IsFunctionLiteral()) { |
| fun = exp->AsFunctionLiteral(); |
| } else if (exp->IsObjectLiteral()) { |
| fun = exp->AsObjectLiteral()->properties()->at(0)->value()-> |
| AsFunctionLiteral(); |
| } else { |
| fun = exp->AsClassLiteral() |
| ->public_members() |
| ->at(0) |
| ->value() |
| ->AsFunctionLiteral(); |
| } |
| } |
| CHECK(!fun->ShouldEagerCompile()); |
| } |
| } |
| |
| |
| const char* ReadString(unsigned* start) { |
| int length = start[0]; |
| char* result = i::NewArray<char>(length + 1); |
| for (int i = 0; i < length; i++) { |
| result[i] = start[i + 1]; |
| } |
| result[length] = '\0'; |
| return result; |
| } |
| |
| enum ParserFlag { |
| kAllowLazy, |
| kAllowNatives, |
| kAllowHarmonyPrivateMethods, |
| kAllowHarmonyLogicalAssignment, |
| }; |
| |
| enum ParserSyncTestResult { |
| kSuccessOrError, |
| kSuccess, |
| kError |
| }; |
| |
| void SetGlobalFlags(base::EnumSet<ParserFlag> flags) { |
| i::FLAG_allow_natives_syntax = flags.contains(kAllowNatives); |
| i::FLAG_harmony_private_methods = flags.contains(kAllowHarmonyPrivateMethods); |
| i::FLAG_harmony_logical_assignment = |
| flags.contains(kAllowHarmonyLogicalAssignment); |
| } |
| |
| void SetParserFlags(i::UnoptimizedCompileFlags* compile_flags, |
| base::EnumSet<ParserFlag> flags) { |
| compile_flags->set_allow_natives_syntax(flags.contains(kAllowNatives)); |
| compile_flags->set_allow_harmony_private_methods( |
| flags.contains(kAllowHarmonyPrivateMethods)); |
| compile_flags->set_allow_harmony_logical_assignment( |
| flags.contains(kAllowHarmonyLogicalAssignment)); |
| } |
| |
| void TestParserSyncWithFlags(i::Handle<i::String> source, |
| base::EnumSet<ParserFlag> flags, |
| ParserSyncTestResult result, |
| bool is_module = false, bool test_preparser = true, |
| bool ignore_error_msg = false) { |
| i::Isolate* isolate = CcTest::i_isolate(); |
| i::Factory* factory = isolate->factory(); |
| i::UnoptimizedCompileState compile_state(isolate); |
| i::UnoptimizedCompileFlags compile_flags = |
| i::UnoptimizedCompileFlags::ForToplevelCompile( |
| isolate, true, LanguageMode::kSloppy, REPLMode::kNo); |
| SetParserFlags(&compile_flags, flags); |
| compile_flags.set_is_module(is_module); |
| |
| uintptr_t stack_limit = isolate->stack_guard()->real_climit(); |
| |
| // Preparse the data. |
| i::PendingCompilationErrorHandler pending_error_handler; |
| if (test_preparser) { |
| std::unique_ptr<i::Utf16CharacterStream> stream( |
| i::ScannerStream::For(isolate, source)); |
| i::Scanner scanner(stream.get(), compile_flags); |
| i::Zone zone(isolate->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory(&zone, isolate->ast_string_constants(), |
| HashSeed(isolate)); |
| i::PreParser preparser(&zone, &scanner, stack_limit, &ast_value_factory, |
| &pending_error_handler, |
| isolate->counters()->runtime_call_stats(), |
| isolate->logger(), compile_flags); |
| scanner.Initialize(); |
| i::PreParser::PreParseResult result = preparser.PreParseProgram(); |
| CHECK_EQ(i::PreParser::kPreParseSuccess, result); |
| } |
| |
| // Parse the data |
| i::FunctionLiteral* function; |
| { |
| SetGlobalFlags(flags); |
| i::Handle<i::Script> script = |
| factory->NewScriptWithId(source, compile_flags.script_id()); |
| i::ParseInfo info(isolate, compile_flags, &compile_state); |
| if (!i::parsing::ParseProgram(&info, script, isolate, |
| parsing::ReportStatisticsMode::kYes)) { |
| info.pending_error_handler()->PrepareErrors(isolate, |
| info.ast_value_factory()); |
| info.pending_error_handler()->ReportErrors(isolate, script); |
| } else { |
| CHECK(!info.pending_error_handler()->has_pending_error()); |
| } |
| function = info.literal(); |
| } |
| |
| // Check that preparsing fails iff parsing fails. |
| if (function == nullptr) { |
| // Extract exception from the parser. |
| CHECK(isolate->has_pending_exception()); |
| i::Handle<i::JSObject> exception_handle( |
| i::JSObject::cast(isolate->pending_exception()), isolate); |
| i::Handle<i::String> message_string = i::Handle<i::String>::cast( |
| i::JSReceiver::GetProperty(isolate, exception_handle, "message") |
| .ToHandleChecked()); |
| isolate->clear_pending_exception(); |
| |
| if (result == kSuccess) { |
| FATAL( |
| "Parser failed on:\n" |
| "\t%s\n" |
| "with error:\n" |
| "\t%s\n" |
| "However, we expected no error.", |
| source->ToCString().get(), message_string->ToCString().get()); |
| } |
| |
| if (test_preparser && !pending_error_handler.has_pending_error() && |
| !pending_error_handler.has_error_unidentifiable_by_preparser()) { |
| FATAL( |
| "Parser failed on:\n" |
| "\t%s\n" |
| "with error:\n" |
| "\t%s\n" |
| "However, the preparser succeeded", |
| source->ToCString().get(), message_string->ToCString().get()); |
| } |
| // Check that preparser and parser produce the same error, except for cases |
| // where we do not track errors in the preparser. |
| if (test_preparser && !ignore_error_msg && |
| !pending_error_handler.has_error_unidentifiable_by_preparser()) { |
| i::Handle<i::String> preparser_message = |
| pending_error_handler.FormatErrorMessageForTest(CcTest::i_isolate()); |
| if (!i::String::Equals(isolate, message_string, preparser_message)) { |
| FATAL( |
| "Expected parser and preparser to produce the same error on:\n" |
| "\t%s\n" |
| "However, found the following error messages\n" |
| "\tparser: %s\n" |
| "\tpreparser: %s\n", |
| source->ToCString().get(), message_string->ToCString().get(), |
| preparser_message->ToCString().get()); |
| } |
| } |
| } else if (test_preparser && pending_error_handler.has_pending_error()) { |
| FATAL( |
| "Preparser failed on:\n" |
| "\t%s\n" |
| "with error:\n" |
| "\t%s\n" |
| "However, the parser succeeded", |
| source->ToCString().get(), |
| pending_error_handler.FormatErrorMessageForTest(CcTest::i_isolate()) |
| ->ToCString() |
| .get()); |
| } else if (result == kError) { |
| FATAL( |
| "Expected error on:\n" |
| "\t%s\n" |
| "However, parser and preparser succeeded", |
| source->ToCString().get()); |
| } |
| } |
| |
| void TestParserSync(const char* source, const ParserFlag* varying_flags, |
| size_t varying_flags_length, |
| ParserSyncTestResult result = kSuccessOrError, |
| const ParserFlag* always_true_flags = nullptr, |
| size_t always_true_flags_length = 0, |
| const ParserFlag* always_false_flags = nullptr, |
| size_t always_false_flags_length = 0, |
| bool is_module = false, bool test_preparser = true, |
| bool ignore_error_msg = false) { |
| i::Handle<i::String> str = |
| CcTest::i_isolate() |
| ->factory() |
| ->NewStringFromUtf8(Vector<const char>(source, strlen(source))) |
| .ToHandleChecked(); |
| for (int bits = 0; bits < (1 << varying_flags_length); bits++) { |
| base::EnumSet<ParserFlag> flags; |
| for (size_t flag_index = 0; flag_index < varying_flags_length; |
| ++flag_index) { |
| if ((bits & (1 << flag_index)) != 0) flags.Add(varying_flags[flag_index]); |
| } |
| for (size_t flag_index = 0; flag_index < always_true_flags_length; |
| ++flag_index) { |
| flags.Add(always_true_flags[flag_index]); |
| } |
| for (size_t flag_index = 0; flag_index < always_false_flags_length; |
| ++flag_index) { |
| flags.Remove(always_false_flags[flag_index]); |
| } |
| TestParserSyncWithFlags(str, flags, result, is_module, test_preparser, |
| ignore_error_msg); |
| } |
| } |
| |
| |
| TEST(ParserSync) { |
| const char* context_data[][2] = {{"", ""}, |
| {"{", "}"}, |
| {"if (true) ", " else {}"}, |
| {"if (true) {} else ", ""}, |
| {"if (true) ", ""}, |
| {"do ", " while (false)"}, |
| {"while (false) ", ""}, |
| {"for (;;) ", ""}, |
| {"with ({})", ""}, |
| {"switch (12) { case 12: ", "}"}, |
| {"switch (12) { default: ", "}"}, |
| {"switch (12) { ", "case 12: }"}, |
| {"label2: ", ""}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = { |
| "{}", "var x", "var x = 1", "const x", "const x = 1", ";", "12", |
| "if (false) {} else ;", "if (false) {} else {}", "if (false) {} else 12", |
| "if (false) ;", "if (false) {}", "if (false) 12", "do {} while (false)", |
| "for (;;) ;", "for (;;) {}", "for (;;) 12", "continue", "continue label", |
| "continue\nlabel", "break", "break label", "break\nlabel", |
| // TODO(marja): activate once parsing 'return' is merged into ParserBase. |
| // "return", |
| // "return 12", |
| // "return\n12", |
| "with ({}) ;", "with ({}) {}", "with ({}) 12", "switch ({}) { default: }", |
| "label3: ", "throw", "throw 12", "throw\n12", "try {} catch(e) {}", |
| "try {} finally {}", "try {} catch(e) {} finally {}", "debugger", |
| nullptr}; |
| |
| const char* termination_data[] = {"", ";", "\n", ";\n", "\n;", nullptr}; |
| |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| CcTest::i_isolate()->stack_guard()->SetStackLimit( |
| i::GetCurrentStackPosition() - 128 * 1024); |
| |
| for (int i = 0; context_data[i][0] != nullptr; ++i) { |
| for (int j = 0; statement_data[j] != nullptr; ++j) { |
| for (int k = 0; termination_data[k] != nullptr; ++k) { |
| int kPrefixLen = static_cast<int>(strlen(context_data[i][0])); |
| int kStatementLen = static_cast<int>(strlen(statement_data[j])); |
| int kTerminationLen = static_cast<int>(strlen(termination_data[k])); |
| int kSuffixLen = static_cast<int>(strlen(context_data[i][1])); |
| int kProgramSize = kPrefixLen + kStatementLen + kTerminationLen + |
| kSuffixLen + |
| static_cast<int>(strlen("label: for (;;) { }")); |
| |
| // Plug the source code pieces together. |
| i::ScopedVector<char> program(kProgramSize + 1); |
| int length = i::SNPrintF(program, |
| "label: for (;;) { %s%s%s%s }", |
| context_data[i][0], |
| statement_data[j], |
| termination_data[k], |
| context_data[i][1]); |
| CHECK_EQ(length, kProgramSize); |
| TestParserSync(program.begin(), nullptr, 0); |
| } |
| } |
| } |
| |
| // Neither Harmony numeric literals nor our natives syntax have any |
| // interaction with the flags above, so test these separately to reduce |
| // the combinatorial explosion. |
| TestParserSync("0o1234", nullptr, 0); |
| TestParserSync("0b1011", nullptr, 0); |
| |
| static const ParserFlag flags3[] = { kAllowNatives }; |
| TestParserSync("%DebugPrint(123)", flags3, arraysize(flags3)); |
| } |
| |
| |
| TEST(StrictOctal) { |
| // Test that syntax error caused by octal literal is reported correctly as |
| // such (issue 2220). |
| v8::V8::Initialize(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Context::Scope context_scope(v8::Context::New(isolate)); |
| |
| v8::TryCatch try_catch(isolate); |
| const char* script = |
| "\"use strict\"; \n" |
| "a = function() { \n" |
| " b = function() { \n" |
| " 01; \n" |
| " }; \n" |
| "}; \n"; |
| CHECK(v8_try_compile(v8_str(script)).IsEmpty()); |
| CHECK(try_catch.HasCaught()); |
| v8::String::Utf8Value exception(isolate, try_catch.Exception()); |
| CHECK_EQ(0, |
| strcmp("SyntaxError: Octal literals are not allowed in strict mode.", |
| *exception)); |
| } |
| |
| void RunParserSyncTest( |
| const char* context_data[][2], const char* statement_data[], |
| ParserSyncTestResult result, const ParserFlag* flags = nullptr, |
| int flags_len = 0, const ParserFlag* always_true_flags = nullptr, |
| int always_true_len = 0, const ParserFlag* always_false_flags = nullptr, |
| int always_false_len = 0, bool is_module = false, |
| bool test_preparser = true, bool ignore_error_msg = false) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| CcTest::i_isolate()->stack_guard()->SetStackLimit( |
| i::GetCurrentStackPosition() - 128 * 1024); |
| |
| // Experimental feature flags should not go here; pass the flags as |
| // always_true_flags if the test needs them. |
| static const ParserFlag default_flags[] = { |
| kAllowLazy, |
| kAllowNatives, |
| }; |
| ParserFlag* generated_flags = nullptr; |
| if (flags == nullptr) { |
| flags = default_flags; |
| flags_len = arraysize(default_flags); |
| if (always_true_flags != nullptr || always_false_flags != nullptr) { |
| // Remove always_true/false_flags from default_flags (if present). |
| CHECK((always_true_flags != nullptr) == (always_true_len > 0)); |
| CHECK((always_false_flags != nullptr) == (always_false_len > 0)); |
| generated_flags = new ParserFlag[flags_len + always_true_len]; |
| int flag_index = 0; |
| for (int i = 0; i < flags_len; ++i) { |
| bool use_flag = true; |
| for (int j = 0; use_flag && j < always_true_len; ++j) { |
| if (flags[i] == always_true_flags[j]) use_flag = false; |
| } |
| for (int j = 0; use_flag && j < always_false_len; ++j) { |
| if (flags[i] == always_false_flags[j]) use_flag = false; |
| } |
| if (use_flag) generated_flags[flag_index++] = flags[i]; |
| } |
| flags_len = flag_index; |
| flags = generated_flags; |
| } |
| } |
| for (int i = 0; context_data[i][0] != nullptr; ++i) { |
| for (int j = 0; statement_data[j] != nullptr; ++j) { |
| int kPrefixLen = static_cast<int>(strlen(context_data[i][0])); |
| int kStatementLen = static_cast<int>(strlen(statement_data[j])); |
| int kSuffixLen = static_cast<int>(strlen(context_data[i][1])); |
| int kProgramSize = kPrefixLen + kStatementLen + kSuffixLen; |
| |
| // Plug the source code pieces together. |
| i::ScopedVector<char> program(kProgramSize + 1); |
| int length = i::SNPrintF(program, |
| "%s%s%s", |
| context_data[i][0], |
| statement_data[j], |
| context_data[i][1]); |
| PrintF("%s\n", program.begin()); |
| CHECK_EQ(length, kProgramSize); |
| TestParserSync(program.begin(), flags, flags_len, result, |
| always_true_flags, always_true_len, always_false_flags, |
| always_false_len, is_module, test_preparser, |
| ignore_error_msg); |
| } |
| } |
| delete[] generated_flags; |
| } |
| |
| void RunModuleParserSyncTest( |
| const char* context_data[][2], const char* statement_data[], |
| ParserSyncTestResult result, const ParserFlag* flags = nullptr, |
| int flags_len = 0, const ParserFlag* always_true_flags = nullptr, |
| int always_true_len = 0, const ParserFlag* always_false_flags = nullptr, |
| int always_false_len = 0, bool test_preparser = true, |
| bool ignore_error_msg = false) { |
| RunParserSyncTest(context_data, statement_data, result, flags, flags_len, |
| always_true_flags, always_true_len, always_false_flags, |
| always_false_len, true, test_preparser, ignore_error_msg); |
| } |
| |
| TEST(NonOctalDecimalIntegerStrictError) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| const char* context_data[][2] = {{"\"use strict\";", ""}, {nullptr, nullptr}}; |
| const char* statement_data[] = {"09", "09.1_2", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError, nullptr, 0, nullptr, |
| 0, nullptr, 0, false, true); |
| } |
| |
| TEST(NumericSeparator) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| const char* context_data[][2] = { |
| {"", ""}, {"\"use strict\";", ""}, {nullptr, nullptr}}; |
| const char* statement_data[] = { |
| "1_0_0_0", "1_0e+1", "1_0e+1_0", "0xF_F_FF", "0o7_7_7", "0b0_1_0_1_0", |
| ".3_2_1", "0.0_2_1", "1_0.0_1", ".0_1_2", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| TEST(NumericSeparatorErrors) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| const char* context_data[][2] = { |
| {"", ""}, {"\"use strict\";", ""}, {nullptr, nullptr}}; |
| const char* statement_data[] = { |
| "1_0_0_0_", "1e_1", "1e+_1", "1_e+1", "1__0", "0x_1", |
| "0x1__1", "0x1_", "0_x1", "0_x_1", "0b_0101", "0b11_", |
| "0b1__1", "0_b1", "0_b_1", "0o777_", "0o_777", "0o7__77", |
| "0.0_2_1_", "0.0__21", "0_.01", "0._01", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError, nullptr, 0, nullptr, |
| 0, nullptr, 0, false, true); |
| } |
| |
| TEST(NumericSeparatorImplicitOctalsErrors) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| const char* context_data[][2] = { |
| {"", ""}, {"\"use strict\";", ""}, {nullptr, nullptr}}; |
| const char* statement_data[] = {"00_122", "0_012", "07_7_7", |
| "0_7_7_7", "0_777", "07_7_7_", |
| "07__77", "0__777", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError, nullptr, 0, nullptr, |
| 0, nullptr, 0, false, true); |
| } |
| |
| TEST(NumericSeparatorNonOctalDecimalInteger) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| const char* context_data[][2] = {{"", ""}, {nullptr, nullptr}}; |
| const char* statement_data[] = {"09.1_2", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess, nullptr, 0, nullptr, |
| 0, nullptr, 0, false, true); |
| } |
| |
| TEST(NumericSeparatorNonOctalDecimalIntegerErrors) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| const char* context_data[][2] = {{"", ""}, {nullptr, nullptr}}; |
| const char* statement_data[] = {"09_12", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError, nullptr, 0, nullptr, |
| 0, nullptr, 0, false, true); |
| } |
| |
| TEST(NumericSeparatorUnicodeEscapeSequencesErrors) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| const char* context_data[][2] = { |
| {"", ""}, {"'use strict'", ""}, {nullptr, nullptr}}; |
| // https://github.com/tc39/proposal-numeric-separator/issues/25 |
| const char* statement_data[] = {"\\u{10_FFFF}", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| TEST(OptionalChaining) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| const char* context_data[][2] = { |
| {"", ""}, {"'use strict';", ""}, {nullptr, nullptr}}; |
| const char* statement_data[] = {"a?.b", "a?.['b']", "a?.()", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| TEST(OptionalChainingTaggedError) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| const char* context_data[][2] = { |
| {"", ""}, {"'use strict';", ""}, {nullptr, nullptr}}; |
| const char* statement_data[] = {"a?.b``", "a?.['b']``", "a?.()``", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| TEST(Nullish) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| const char* context_data[][2] = { |
| {"", ""}, {"'use strict';", ""}, {nullptr, nullptr}}; |
| const char* statement_data[] = {"a ?? b", "a ?? b ?? c", |
| "a ?? b ? c : d" |
| "a ?? b ?? c ? d : e", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| TEST(NullishNotContained) { |
| v8::HandleScope handles(CcTest::isolate()); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| const char* context_data[][2] = { |
| {"", ""}, {"'use strict';", ""}, {nullptr, nullptr}}; |
| const char* statement_data[] = {"a || b ?? c", "a ?? b || c", |
| "a && b ?? c" |
| "a ?? b && c", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| TEST(ErrorsEvalAndArguments) { |
| // Tests that both preparsing and parsing produce the right kind of errors for |
| // using "eval" and "arguments" as identifiers. Without the strict mode, it's |
| // ok to use "eval" or "arguments" as identifiers. With the strict mode, it |
| // isn't. |
| const char* context_data[][2] = { |
| {"\"use strict\";", ""}, |
| {"var eval; function test_func() {\"use strict\"; ", "}"}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"var eval;", |
| "var arguments", |
| "var foo, eval;", |
| "var foo, arguments;", |
| "try { } catch (eval) { }", |
| "try { } catch (arguments) { }", |
| "function eval() { }", |
| "function arguments() { }", |
| "function foo(eval) { }", |
| "function foo(arguments) { }", |
| "function foo(bar, eval) { }", |
| "function foo(bar, arguments) { }", |
| "(eval) => { }", |
| "(arguments) => { }", |
| "(foo, eval) => { }", |
| "(foo, arguments) => { }", |
| "eval = 1;", |
| "arguments = 1;", |
| "var foo = eval = 1;", |
| "var foo = arguments = 1;", |
| "++eval;", |
| "++arguments;", |
| "eval++;", |
| "arguments++;", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(NoErrorsEvalAndArgumentsSloppy) { |
| // Tests that both preparsing and parsing accept "eval" and "arguments" as |
| // identifiers when needed. |
| const char* context_data[][2] = { |
| {"", ""}, {"function test_func() {", "}"}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"var eval;", |
| "var arguments", |
| "var foo, eval;", |
| "var foo, arguments;", |
| "try { } catch (eval) { }", |
| "try { } catch (arguments) { }", |
| "function eval() { }", |
| "function arguments() { }", |
| "function foo(eval) { }", |
| "function foo(arguments) { }", |
| "function foo(bar, eval) { }", |
| "function foo(bar, arguments) { }", |
| "eval = 1;", |
| "arguments = 1;", |
| "var foo = eval = 1;", |
| "var foo = arguments = 1;", |
| "++eval;", |
| "++arguments;", |
| "eval++;", |
| "arguments++;", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(NoErrorsEvalAndArgumentsStrict) { |
| const char* context_data[][2] = { |
| {"\"use strict\";", ""}, |
| {"function test_func() { \"use strict\";", "}"}, |
| {"() => { \"use strict\"; ", "}"}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"eval;", |
| "arguments;", |
| "var foo = eval;", |
| "var foo = arguments;", |
| "var foo = { eval: 1 };", |
| "var foo = { arguments: 1 };", |
| "var foo = { }; foo.eval = {};", |
| "var foo = { }; foo.arguments = {};", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| #define FUTURE_STRICT_RESERVED_WORDS_NO_LET(V) \ |
| V(implements) \ |
| V(interface) \ |
| V(package) \ |
| V(private) \ |
| V(protected) \ |
| V(public) \ |
| V(static) \ |
| V(yield) |
| |
| #define FUTURE_STRICT_RESERVED_WORDS(V) \ |
| V(let) \ |
| FUTURE_STRICT_RESERVED_WORDS_NO_LET(V) |
| |
| #define LIMITED_FUTURE_STRICT_RESERVED_WORDS_NO_LET(V) \ |
| V(implements) \ |
| V(static) \ |
| V(yield) |
| |
| #define LIMITED_FUTURE_STRICT_RESERVED_WORDS(V) \ |
| V(let) \ |
| LIMITED_FUTURE_STRICT_RESERVED_WORDS_NO_LET(V) |
| |
| #define FUTURE_STRICT_RESERVED_STATEMENTS(NAME) \ |
| "var " #NAME ";", \ |
| "var foo, " #NAME ";", \ |
| "try { } catch (" #NAME ") { }", \ |
| "function " #NAME "() { }", \ |
| "(function " #NAME "() { })", \ |
| "function foo(" #NAME ") { }", \ |
| "function foo(bar, " #NAME ") { }", \ |
| #NAME " = 1;", \ |
| #NAME " += 1;", \ |
| "var foo = " #NAME " = 1;", \ |
| "++" #NAME ";", \ |
| #NAME " ++;", |
| |
| // clang-format off |
| #define FUTURE_STRICT_RESERVED_LEX_BINDINGS(NAME) \ |
| "let " #NAME ";", \ |
| "for (let " #NAME "; false; ) {}", \ |
| "for (let " #NAME " in {}) {}", \ |
| "for (let " #NAME " of []) {}", \ |
| "const " #NAME " = null;", \ |
| "for (const " #NAME " = null; false; ) {}", \ |
| "for (const " #NAME " in {}) {}", \ |
| "for (const " #NAME " of []) {}", |
| // clang-format on |
| |
| TEST(ErrorsFutureStrictReservedWords) { |
| // Tests that both preparsing and parsing produce the right kind of errors for |
| // using future strict reserved words as identifiers. Without the strict mode, |
| // it's ok to use future strict reserved words as identifiers. With the strict |
| // mode, it isn't. |
| const char* strict_contexts[][2] = { |
| {"function test_func() {\"use strict\"; ", "}"}, |
| {"() => { \"use strict\"; ", "}"}, |
| {nullptr, nullptr}}; |
| |
| // clang-format off |
| const char* statement_data[] { |
| LIMITED_FUTURE_STRICT_RESERVED_WORDS(FUTURE_STRICT_RESERVED_STATEMENTS) |
| LIMITED_FUTURE_STRICT_RESERVED_WORDS(FUTURE_STRICT_RESERVED_LEX_BINDINGS) |
| nullptr |
| }; |
| // clang-format on |
| |
| RunParserSyncTest(strict_contexts, statement_data, kError); |
| |
| // From ES2015, 13.3.1.1 Static Semantics: Early Errors: |
| // |
| // > LexicalDeclaration : LetOrConst BindingList ; |
| // > |
| // > - It is a Syntax Error if the BoundNames of BindingList contains "let". |
| const char* non_strict_contexts[][2] = {{"", ""}, |
| {"function test_func() {", "}"}, |
| {"() => {", "}"}, |
| {nullptr, nullptr}}; |
| const char* invalid_statements[] = { |
| FUTURE_STRICT_RESERVED_LEX_BINDINGS(let) nullptr}; |
| |
| RunParserSyncTest(non_strict_contexts, invalid_statements, kError); |
| } |
| |
| #undef LIMITED_FUTURE_STRICT_RESERVED_WORDS |
| |
| |
| TEST(NoErrorsFutureStrictReservedWords) { |
| const char* context_data[][2] = {{"", ""}, |
| {"function test_func() {", "}"}, |
| {"() => {", "}"}, |
| {nullptr, nullptr}}; |
| |
| // clang-format off |
| const char* statement_data[] = { |
| FUTURE_STRICT_RESERVED_WORDS(FUTURE_STRICT_RESERVED_STATEMENTS) |
| FUTURE_STRICT_RESERVED_WORDS_NO_LET(FUTURE_STRICT_RESERVED_LEX_BINDINGS) |
| nullptr |
| }; |
| // clang-format on |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(ErrorsReservedWords) { |
| // Tests that both preparsing and parsing produce the right kind of errors for |
| // using future reserved words as identifiers. These tests don't depend on the |
| // strict mode. |
| const char* context_data[][2] = { |
| {"", ""}, |
| {"\"use strict\";", ""}, |
| {"var eval; function test_func() {", "}"}, |
| {"var eval; function test_func() {\"use strict\"; ", "}"}, |
| {"var eval; () => {", "}"}, |
| {"var eval; () => {\"use strict\"; ", "}"}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"var super;", |
| "var foo, super;", |
| "try { } catch (super) { }", |
| "function super() { }", |
| "function foo(super) { }", |
| "function foo(bar, super) { }", |
| "(super) => { }", |
| "(bar, super) => { }", |
| "super = 1;", |
| "var foo = super = 1;", |
| "++super;", |
| "super++;", |
| "function foo super", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(NoErrorsLetSloppyAllModes) { |
| // In sloppy mode, it's okay to use "let" as identifier. |
| const char* context_data[][2] = {{"", ""}, |
| {"function f() {", "}"}, |
| {"(function f() {", "})"}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = { |
| "var let;", |
| "var foo, let;", |
| "try { } catch (let) { }", |
| "function let() { }", |
| "(function let() { })", |
| "function foo(let) { }", |
| "function foo(bar, let) { }", |
| "let = 1;", |
| "var foo = let = 1;", |
| "let * 2;", |
| "++let;", |
| "let++;", |
| "let: 34", |
| "function let(let) { let: let(let + let(0)); }", |
| "({ let: 1 })", |
| "({ get let() { 1 } })", |
| "let(100)", |
| "L: let\nx", |
| "L: let\n{x}", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(NoErrorsYieldSloppyAllModes) { |
| // In sloppy mode, it's okay to use "yield" as identifier, *except* inside a |
| // generator (see other test). |
| const char* context_data[][2] = {{"", ""}, |
| {"function not_gen() {", "}"}, |
| {"(function not_gen() {", "})"}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = { |
| "var yield;", |
| "var foo, yield;", |
| "try { } catch (yield) { }", |
| "function yield() { }", |
| "(function yield() { })", |
| "function foo(yield) { }", |
| "function foo(bar, yield) { }", |
| "yield = 1;", |
| "var foo = yield = 1;", |
| "yield * 2;", |
| "++yield;", |
| "yield++;", |
| "yield: 34", |
| "function yield(yield) { yield: yield (yield + yield(0)); }", |
| "({ yield: 1 })", |
| "({ get yield() { 1 } })", |
| "yield(100)", |
| "yield[100]", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(NoErrorsYieldSloppyGeneratorsEnabled) { |
| // In sloppy mode, it's okay to use "yield" as identifier, *except* inside a |
| // generator (see next test). |
| const char* context_data[][2] = { |
| {"", ""}, |
| {"function not_gen() {", "}"}, |
| {"function * gen() { function not_gen() {", "} }"}, |
| {"(function not_gen() {", "})"}, |
| {"(function * gen() { (function not_gen() {", "}) })"}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = { |
| "var yield;", |
| "var foo, yield;", |
| "try { } catch (yield) { }", |
| "function yield() { }", |
| "(function yield() { })", |
| "function foo(yield) { }", |
| "function foo(bar, yield) { }", |
| "function * yield() { }", |
| "yield = 1;", |
| "var foo = yield = 1;", |
| "yield * 2;", |
| "++yield;", |
| "yield++;", |
| "yield: 34", |
| "function yield(yield) { yield: yield (yield + yield(0)); }", |
| "({ yield: 1 })", |
| "({ get yield() { 1 } })", |
| "yield(100)", |
| "yield[100]", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(ErrorsYieldStrict) { |
| const char* context_data[][2] = { |
| {"\"use strict\";", ""}, |
| {"\"use strict\"; function not_gen() {", "}"}, |
| {"function test_func() {\"use strict\"; ", "}"}, |
| {"\"use strict\"; function * gen() { function not_gen() {", "} }"}, |
| {"\"use strict\"; (function not_gen() {", "})"}, |
| {"\"use strict\"; (function * gen() { (function not_gen() {", "}) })"}, |
| {"() => {\"use strict\"; ", "}"}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"var yield;", |
| "var foo, yield;", |
| "try { } catch (yield) { }", |
| "function yield() { }", |
| "(function yield() { })", |
| "function foo(yield) { }", |
| "function foo(bar, yield) { }", |
| "function * yield() { }", |
| "(function * yield() { })", |
| "yield = 1;", |
| "var foo = yield = 1;", |
| "++yield;", |
| "yield++;", |
| "yield: 34;", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(ErrorsYieldSloppy) { |
| const char* context_data[][2] = {{"", ""}, |
| {"function not_gen() {", "}"}, |
| {"(function not_gen() {", "})"}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"(function * yield() { })", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(NoErrorsGenerator) { |
| // clang-format off |
| const char* context_data[][2] = { |
| { "function * gen() {", "}" }, |
| { "(function * gen() {", "})" }, |
| { "(function * () {", "})" }, |
| { nullptr, nullptr } |
| }; |
| |
| const char* statement_data[] = { |
| // A generator without a body is valid. |
| "" |
| // Valid yield expressions inside generators. |
| "yield 2;", |
| "yield * 2;", |
| "yield * \n 2;", |
| "yield yield 1;", |
| "yield * yield * 1;", |
| "yield 3 + (yield 4);", |
| "yield * 3 + (yield * 4);", |
| "(yield * 3) + (yield * 4);", |
| "yield 3; yield 4;", |
| "yield * 3; yield * 4;", |
| "(function (yield) { })", |
| "(function yield() { })", |
| "yield { yield: 12 }", |
| "yield /* comment */ { yield: 12 }", |
| "yield * \n { yield: 12 }", |
| "yield /* comment */ * \n { yield: 12 }", |
| // You can return in a generator. |
| "yield 1; return", |
| "yield * 1; return", |
| "yield 1; return 37", |
| "yield * 1; return 37", |
| "yield 1; return 37; yield 'dead';", |
| "yield * 1; return 37; yield * 'dead';", |
| // Yield is still a valid key in object literals. |
| "({ yield: 1 })", |
| "({ get yield() { } })", |
| // And in assignment pattern computed properties |
| "({ [yield]: x } = { })", |
| // Yield without RHS. |
| "yield;", |
| "yield", |
| "yield\n", |
| "yield /* comment */" |
| "yield // comment\n" |
| "(yield)", |
| "[yield]", |
| "{yield}", |
| "yield, yield", |
| "yield; yield", |
| "(yield) ? yield : yield", |
| "(yield) \n ? yield : yield", |
| // If there is a newline before the next token, we don't look for RHS. |
| "yield\nfor (;;) {}", |
| "x = class extends (yield) {}", |
| "x = class extends f(yield) {}", |
| "x = class extends (null, yield) { }", |
| "x = class extends (a ? null : yield) { }", |
| nullptr |
| }; |
| // clang-format on |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(ErrorsYieldGenerator) { |
| // clang-format off |
| const char* context_data[][2] = { |
| { "function * gen() {", "}" }, |
| { "\"use strict\"; function * gen() {", "}" }, |
| { nullptr, nullptr } |
| }; |
| |
| const char* statement_data[] = { |
| // Invalid yield expressions inside generators. |
| "var yield;", |
| "var foo, yield;", |
| "try { } catch (yield) { }", |
| "function yield() { }", |
| // The name of the NFE is bound in the generator, which does not permit |
| // yield to be an identifier. |
| "(function * yield() { })", |
| // Yield isn't valid as a formal parameter for generators. |
| "function * foo(yield) { }", |
| "(function * foo(yield) { })", |
| "yield = 1;", |
| "var foo = yield = 1;", |
| "++yield;", |
| "yield++;", |
| "yield *", |
| "(yield *)", |
| // Yield binds very loosely, so this parses as "yield (3 + yield 4)", which |
| // is invalid. |
| "yield 3 + yield 4;", |
| "yield: 34", |
| "yield ? 1 : 2", |
| // Parses as yield (/ yield): invalid. |
| "yield / yield", |
| "+ yield", |
| "+ yield 3", |
| // Invalid (no newline allowed between yield and *). |
| "yield\n*3", |
| // Invalid (we see a newline, so we parse {yield:42} as a statement, not an |
| // object literal, and yield is not a valid label). |
| "yield\n{yield: 42}", |
| "yield /* comment */\n {yield: 42}", |
| "yield //comment\n {yield: 42}", |
| // Destructuring binding and assignment are both disallowed |
| "var [yield] = [42];", |
| "var {foo: yield} = {a: 42};", |
| "[yield] = [42];", |
| "({a: yield} = {a: 42});", |
| // Also disallow full yield expressions on LHS |
| "var [yield 24] = [42];", |
| "var {foo: yield 24} = {a: 42};", |
| "[yield 24] = [42];", |
| "({a: yield 24} = {a: 42});", |
| "for (yield 'x' in {});", |
| "for (yield 'x' of {});", |
| "for (yield 'x' in {} in {});", |
| "for (yield 'x' in {} of {});", |
| "class C extends yield { }", |
| nullptr |
| }; |
| // clang-format on |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(ErrorsNameOfStrictFunction) { |
| // Tests that illegal tokens as names of a strict function produce the correct |
| // errors. |
| const char* context_data[][2] = {{"function ", ""}, |
| {"\"use strict\"; function", ""}, |
| {"function * ", ""}, |
| {"\"use strict\"; function * ", ""}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = { |
| "eval() {\"use strict\";}", "arguments() {\"use strict\";}", |
| "interface() {\"use strict\";}", "yield() {\"use strict\";}", |
| // Future reserved words are always illegal |
| "super() { }", "super() {\"use strict\";}", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(NoErrorsNameOfStrictFunction) { |
| const char* context_data[][2] = {{"function ", ""}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"eval() { }", "arguments() { }", |
| "interface() { }", "yield() { }", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(NoErrorsNameOfStrictGenerator) { |
| const char* context_data[][2] = {{"function * ", ""}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"eval() { }", "arguments() { }", |
| "interface() { }", "yield() { }", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(ErrorsIllegalWordsAsLabelsSloppy) { |
| // Using future reserved words as labels is always an error. |
| const char* context_data[][2] = {{"", ""}, |
| {"function test_func() {", "}"}, |
| {"() => {", "}"}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"super: while(true) { break super; }", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(ErrorsIllegalWordsAsLabelsStrict) { |
| // Tests that illegal tokens as labels produce the correct errors. |
| const char* context_data[][2] = { |
| {"\"use strict\";", ""}, |
| {"function test_func() {\"use strict\"; ", "}"}, |
| {"() => {\"use strict\"; ", "}"}, |
| {nullptr, nullptr}}; |
| |
| #define LABELLED_WHILE(NAME) #NAME ": while (true) { break " #NAME "; }", |
| const char* statement_data[] = { |
| "super: while(true) { break super; }", |
| FUTURE_STRICT_RESERVED_WORDS(LABELLED_WHILE) nullptr}; |
| #undef LABELLED_WHILE |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(NoErrorsIllegalWordsAsLabels) { |
| // Using eval and arguments as labels is legal even in strict mode. |
| const char* context_data[][2] = { |
| {"", ""}, |
| {"function test_func() {", "}"}, |
| {"() => {", "}"}, |
| {"\"use strict\";", ""}, |
| {"\"use strict\"; function test_func() {", "}"}, |
| {"\"use strict\"; () => {", "}"}, |
| {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"mylabel: while(true) { break mylabel; }", |
| "eval: while(true) { break eval; }", |
| "arguments: while(true) { break arguments; }", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(NoErrorsFutureStrictReservedAsLabelsSloppy) { |
| const char* context_data[][2] = {{"", ""}, |
| {"function test_func() {", "}"}, |
| {"() => {", "}"}, |
| {nullptr, nullptr}}; |
| |
| #define LABELLED_WHILE(NAME) #NAME ": while (true) { break " #NAME "; }", |
| const char* statement_data[]{ |
| FUTURE_STRICT_RESERVED_WORDS(LABELLED_WHILE) nullptr}; |
| #undef LABELLED_WHILE |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(ErrorsParenthesizedLabels) { |
| // Parenthesized identifiers shouldn't be recognized as labels. |
| const char* context_data[][2] = {{"", ""}, |
| {"function test_func() {", "}"}, |
| {"() => {", "}"}, |
| {nullptr, |