| // 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/v8.h" |
| |
| #include "src/api.h" |
| #include "src/ast/ast-numbering.h" |
| #include "src/ast/ast-value-factory.h" |
| #include "src/ast/ast.h" |
| #include "src/compiler.h" |
| #include "src/execution.h" |
| #include "src/flags.h" |
| #include "src/isolate.h" |
| #include "src/objects-inl.h" |
| #include "src/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/unicode-cache.h" |
| #include "src/utils.h" |
| |
| #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 |
| |
| 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, IGNORE_TOKEN) |
| #undef KEYWORD |
| {nullptr, i::Token::IDENTIFIER}}; |
| |
| KeywordToken key_token; |
| i::UnicodeCache unicode_cache; |
| 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(&unicode_cache); |
| scanner.Initialize(stream.get(), false); |
| 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(&unicode_cache); |
| scanner.Initialize(stream.get(), false); |
| 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(&unicode_cache); |
| scanner.Initialize(stream.get(), false); |
| 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(&unicode_cache); |
| scanner.Initialize(stream.get(), false); |
| 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); |
| |
| // 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(i_isolate->unicode_cache()); |
| scanner.Initialize(stream.get(), false); |
| i::Zone zone(i_isolate->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory(&zone, |
| i_isolate->ast_string_constants(), |
| i_isolate->heap()->HashSeed()); |
| 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()); |
| 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(i_isolate->unicode_cache()); |
| scanner.Initialize(stream.get(), false); |
| i::Zone zone(i_isolate->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory(&zone, |
| i_isolate->ast_string_constants(), |
| i_isolate->heap()->HashSeed()); |
| 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()); |
| 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()); |
| } |
| } |
| |
| TEST(ScanHtmlComments) { |
| const char* src = "a <!-- b --> c"; |
| i::UnicodeCache unicode_cache; |
| |
| // Disallow HTML comments. |
| { |
| auto stream = i::ScannerStream::ForTesting(src); |
| i::Scanner scanner(&unicode_cache); |
| scanner.Initialize(stream.get(), true); |
| CHECK_EQ(i::Token::IDENTIFIER, scanner.Next()); |
| CHECK_EQ(i::Token::ILLEGAL, scanner.Next()); |
| } |
| |
| // Skip HTML comments: |
| { |
| auto stream = i::ScannerStream::ForTesting(src); |
| i::Scanner scanner(&unicode_cache); |
| scanner.Initialize(stream.get(), false); |
| 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 { return data_; } |
| size_t length() const { return length_; } |
| |
| private: |
| const char* data_; |
| size_t length_; |
| }; |
| |
| |
| TEST(UsingCachedData) { |
| // Producing cached parser data while parsing eagerly is not supported. |
| if (!i::FLAG_lazy) return; |
| |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope handles(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| v8::Context::Scope context_scope(context); |
| CcTest::i_isolate()->stack_guard()->SetStackLimit( |
| i::GetCurrentStackPosition() - 128 * 1024); |
| |
| // Source containing functions that might be lazily compiled and all types |
| // of symbols (string, propertyName, regexp). |
| const char* source = |
| "var x = 42;" |
| "function foo(a) { return function nolazy(b) { return a + b; } }" |
| "function bar(a) { if (a) return function lazy(b) { return b; } }" |
| "var z = {'string': 'string literal', bareword: 'propertyName', " |
| " 42: 'number literal', for: 'keyword as propertyName', " |
| " f\\u006fr: 'keyword propertyname with escape'};" |
| "var v = /RegExp Literal/;" |
| "var w = /RegExp Literal\\u0020With Escape/gi;" |
| "var y = { get getter() { return 42; }, " |
| " set setter(v) { this.value = v; }};" |
| "var f = a => function (b) { return a + b; };" |
| "var g = a => b => a + b;"; |
| int source_length = i::StrLength(source); |
| |
| // ScriptResource will be deleted when the corresponding String is GCd. |
| v8::ScriptCompiler::Source script_source( |
| v8::String::NewExternalOneByte(isolate, |
| new ScriptResource(source, source_length)) |
| .ToLocalChecked()); |
| v8::ScriptCompiler::Compile(isolate->GetCurrentContext(), &script_source, |
| v8::ScriptCompiler::kProduceParserCache) |
| .ToLocalChecked(); |
| CHECK(script_source.GetCachedData()); |
| |
| // Compile the script again, using the cached data. |
| bool lazy_flag = i::FLAG_lazy; |
| i::FLAG_lazy = true; |
| v8::ScriptCompiler::Compile(isolate->GetCurrentContext(), &script_source, |
| v8::ScriptCompiler::kConsumeParserCache) |
| .ToLocalChecked(); |
| i::FLAG_lazy = false; |
| v8::ScriptCompiler::CompileUnboundScript( |
| isolate, &script_source, v8::ScriptCompiler::kConsumeParserCache) |
| .ToLocalChecked(); |
| i::FLAG_lazy = lazy_flag; |
| } |
| |
| |
| TEST(PreparseFunctionDataIsUsed) { |
| // Producing cached parser data while parsing eagerly is not supported. |
| if (!i::FLAG_lazy) return; |
| |
| // This tests that we actually do use the function data generated by the |
| // preparser. |
| |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope handles(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| v8::Context::Scope context_scope(context); |
| CcTest::i_isolate()->stack_guard()->SetStackLimit( |
| i::GetCurrentStackPosition() - 128 * 1024); |
| |
| const char* good_code[] = { |
| "function z() { var a; } function f() { return 25; } f();", |
| "var z = function () { var a; }; function f() { return 25; } f();", |
| "function *z() { var a; } function f() { return 25; } f();", |
| "var z = function *() { var a; }; function f() { return 25; } f();", |
| "function z(p1, p2) { var a; } function f() { return 25; } f();", |
| "var z = function (p1, p2) { var a; }; function f() { return 25; } f();", |
| "function *z(p1, p2) { var a; } function f() { return 25; } f();", |
| "var z = function *(p1, p2) { var a; }; function f() { return 25; } f();", |
| "var z = () => { var a; }; function f() { return 25; } f();", |
| "var z = (p1, p2) => { var a; }; function f() { return 25; } f();", |
| }; |
| |
| // Insert a syntax error inside the lazy function. |
| const char* bad_code[] = { |
| "function z() { if ( } function f() { return 25; } f();", |
| "var z = function () { if ( }; function f() { return 25; } f();", |
| "function *z() { if ( } function f() { return 25; } f();", |
| "var z = function *() { if ( }; function f() { return 25; } f();", |
| "function z(p1, p2) { if ( } function f() { return 25; } f();", |
| "var z = function (p1, p2) { if ( }; function f() { return 25; } f();", |
| "function *z(p1, p2) { if ( } function f() { return 25; } f();", |
| "var z = function *(p1, p2) { if ( }; function f() { return 25; } f();", |
| "var z = () => { if ( }; function f() { return 25; } f();", |
| "var z = (p1, p2) => { if ( }; function f() { return 25; } f();", |
| }; |
| |
| for (unsigned i = 0; i < arraysize(good_code); i++) { |
| v8::ScriptCompiler::Source good_source(v8_str(good_code[i])); |
| v8::ScriptCompiler::Compile(isolate->GetCurrentContext(), &good_source, |
| v8::ScriptCompiler::kProduceParserCache) |
| .ToLocalChecked(); |
| |
| const v8::ScriptCompiler::CachedData* cached_data = |
| good_source.GetCachedData(); |
| CHECK_NOT_NULL(cached_data->data); |
| CHECK_GT(cached_data->length, 0); |
| |
| // Now compile the erroneous code with the good preparse data. If the |
| // preparse data is used, the lazy function is skipped and it should |
| // compile fine. |
| v8::ScriptCompiler::Source bad_source( |
| v8_str(bad_code[i]), new v8::ScriptCompiler::CachedData( |
| cached_data->data, cached_data->length)); |
| v8::Local<v8::Value> result = |
| CompileRun(isolate->GetCurrentContext(), &bad_source, |
| v8::ScriptCompiler::kConsumeParserCache); |
| CHECK(result->IsInt32()); |
| CHECK_EQ(25, result->Int32Value(isolate->GetCurrentContext()).FromJust()); |
| } |
| } |
| |
| |
| TEST(StandAlonePreParser) { |
| v8::V8::Initialize(); |
| i::Isolate* i_isolate = CcTest::i_isolate(); |
| |
| 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(i_isolate->unicode_cache()); |
| scanner.Initialize(stream.get(), false); |
| |
| i::Zone zone(i_isolate->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory(&zone, |
| i_isolate->ast_string_constants(), |
| i_isolate->heap()->HashSeed()); |
| 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()); |
| preparser.set_allow_natives(true); |
| 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(); |
| CcTest::i_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(isolate->unicode_cache()); |
| scanner.Initialize(stream.get(), false); |
| |
| // Preparser defaults to disallowing natives syntax. |
| i::Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory( |
| &zone, CcTest::i_isolate()->ast_string_constants(), |
| CcTest::i_isolate()->heap()->HashSeed()); |
| 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()); |
| i::PreParser::PreParseResult result = preparser.PreParseProgram(); |
| CHECK_EQ(i::PreParser::kPreParseSuccess, result); |
| CHECK(pending_error_handler.has_pending_error()); |
| } |
| } |
| |
| |
| TEST(PreparsingObjectLiterals) { |
| // Regression test for a bug where the symbol stream produced by PreParser |
| // didn't match what Parser wanted to consume. |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope handles(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| v8::Context::Scope context_scope(context); |
| CcTest::i_isolate()->stack_guard()->SetStackLimit( |
| i::GetCurrentStackPosition() - 128 * 1024); |
| |
| { |
| const char* source = "var myo = {if: \"foo\"}; myo.if;"; |
| v8::Local<v8::Value> result = ParserCacheCompileRun(source); |
| CHECK(result->IsString()); |
| v8::String::Utf8Value utf8(isolate, result); |
| CHECK_EQ(0, strcmp("foo", *utf8)); |
| } |
| |
| { |
| const char* source = "var myo = {\"bar\": \"foo\"}; myo[\"bar\"];"; |
| v8::Local<v8::Value> result = ParserCacheCompileRun(source); |
| CHECK(result->IsString()); |
| v8::String::Utf8Value utf8(isolate, result); |
| CHECK_EQ(0, strcmp("foo", *utf8)); |
| } |
| |
| { |
| const char* source = "var myo = {1: \"foo\"}; myo[1];"; |
| v8::Local<v8::Value> result = ParserCacheCompileRun(source); |
| CHECK(result->IsString()); |
| v8::String::Utf8Value utf8(isolate, result); |
| CHECK_EQ(0, strcmp("foo", *utf8)); |
| } |
| } |
| |
| |
| TEST(RegressChromium62639) { |
| v8::V8::Initialize(); |
| i::Isolate* isolate = CcTest::i_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(CcTest::i_isolate()->unicode_cache()); |
| scanner.Initialize(stream.get(), false); |
| i::Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory( |
| &zone, CcTest::i_isolate()->ast_string_constants(), |
| CcTest::i_isolate()->heap()->HashSeed()); |
| i::PendingCompilationErrorHandler pending_error_handler; |
| i::PreParser preparser( |
| &zone, &scanner, CcTest::i_isolate()->stack_guard()->real_climit(), |
| &ast_value_factory, &pending_error_handler, |
| isolate->counters()->runtime_call_stats(), isolate->logger()); |
| 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()); |
| } |
| |
| |
| TEST(Regress928) { |
| // Test only applies when lazy parsing. |
| if (!i::FLAG_lazy) return; |
| |
| // Tests that the first non-toplevel function is not included in the preparse |
| // data. |
| const char* program = |
| "try { } catch (e) { var foo = function () { /* first */ } }" |
| "var bar = function () { /* second */ }"; |
| |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope handles(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate); |
| v8::Context::Scope context_scope(context); |
| v8::ScriptCompiler::Source script_source(v8_str(program)); |
| v8::ScriptCompiler::Compile(context, &script_source, |
| v8::ScriptCompiler::kProduceParserCache) |
| .ToLocalChecked(); |
| |
| const v8::ScriptCompiler::CachedData* cached_data = |
| script_source.GetCachedData(); |
| i::ScriptData script_data(cached_data->data, cached_data->length); |
| std::unique_ptr<i::ParseData> pd(i::ParseData::FromCachedData(&script_data)); |
| pd->Initialize(); |
| |
| int first_function = |
| static_cast<int>(strstr(program, "function") - program); |
| int first_lparen = first_function + i::StrLength("function "); |
| CHECK_EQ('(', program[first_lparen]); |
| i::FunctionEntry entry1 = pd->GetFunctionEntry(first_lparen); |
| CHECK(!entry1.is_valid()); |
| |
| int second_function = |
| static_cast<int>(strstr(program + first_lparen, "function") - program); |
| int second_lparen = second_function + i::StrLength("function "); |
| CHECK_EQ('(', program[second_lparen]); |
| i::FunctionEntry entry2 = pd->GetFunctionEntry(second_lparen); |
| CHECK(entry2.is_valid()); |
| CHECK_EQ('}', program[entry2.end_pos() - 1]); |
| } |
| |
| |
| TEST(PreParseOverflow) { |
| v8::V8::Initialize(); |
| i::Isolate* isolate = CcTest::i_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(isolate->unicode_cache()); |
| scanner.Initialize(stream.get(), false); |
| |
| i::Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory( |
| &zone, CcTest::i_isolate()->ast_string_constants(), |
| CcTest::i_isolate()->heap()->HashSeed()); |
| 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()); |
| 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::Scanner scanner(CcTest::i_isolate()->unicode_cache()); |
| scanner.Initialize(stream, false); |
| |
| 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::IDENTIFIER, |
| 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::Scanner scanner(CcTest::i_isolate()->unicode_cache()); |
| scanner.Initialize(stream.get(), false); |
| |
| 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(), |
| CcTest::i_isolate()->heap()->HashSeed()); |
| 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(); |
| 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 = i::StrLength(surroundings[j].prefix) + |
| i::StrLength(surroundings[j].suffix) + |
| i::StrLength(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.start())) |
| .ToHandleChecked(); |
| i::Handle<i::Script> script = factory->NewScript(source); |
| i::ParseInfo info(script); |
| // The information we're checking is only produced when eager parsing. |
| info.set_allow_lazy_parsing(false); |
| CHECK(i::parsing::ParseProgram(&info, isolate)); |
| CHECK(i::Rewriter::Rewrite(&info)); |
| info.ast_value_factory()->Internalize(isolate); |
| i::DeclarationScope::Analyze(&info); |
| i::DeclarationScope::AllocateScopeInfos(&info, isolate, |
| i::AnalyzeMode::kRegular); |
| 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->Lookup(info.ast_value_factory()->this_string())->is_used()); |
| } |
| if (is_sloppy(scope->language_mode())) { |
| CHECK_EQ((source_data[i].expected & EVAL) != 0, |
| scope->AsDeclarationScope()->calls_sloppy_eval()); |
| } |
| } |
| } |
| } |
| |
| 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::ParseInfo info(script); |
| info.set_allow_lazy_parsing(false); |
| info.set_toplevel(true); |
| |
| CHECK(i::parsing::ParseProgram(&info, 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 = i::StrLength(source_data[i].outer_prefix); |
| int kInnerByteLen = i::StrLength(source_data[i].inner_source); |
| int kSuffixByteLen = i::StrLength(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.start())).ToHandleChecked(); |
| CHECK_EQ(source->length(), kProgramSize); |
| i::Handle<i::Script> script = factory->NewScript(source); |
| i::ParseInfo info(script); |
| info.set_language_mode(source_data[i].language_mode); |
| i::parsing::ParseProgram(&info, isolate); |
| CHECK_NOT_NULL(info.literal()); |
| |
| // 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::ParseInfo info(script); |
| i::parsing::ParseProgram(&info, isolate); |
| function = info.literal(); |
| CHECK_NOT_NULL(function); |
| CHECK_NOT_NULL(function->body()); |
| CHECK_EQ(1, function->body()->length()); |
| i::FunctionLiteral* inner = |
| function->body()->first()->AsExpressionStatement()->expression()-> |
| 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_NOT_NULL(inner->body()); |
| 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()->properties()->at(0)->value()-> |
| AsFunctionLiteral(); |
| } |
| } |
| CHECK_NULL(fun->body()); |
| } |
| } |
| |
| |
| 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, |
| kAllowHarmonyFunctionSent, |
| kAllowHarmonyPublicFields, |
| kAllowHarmonyPrivateFields, |
| kAllowHarmonyStaticFields, |
| kAllowHarmonyDynamicImport, |
| kAllowHarmonyImportMeta, |
| kAllowHarmonyDoExpressions, |
| kAllowHarmonyOptionalCatchBinding, |
| }; |
| |
| enum ParserSyncTestResult { |
| kSuccessOrError, |
| kSuccess, |
| kError |
| }; |
| |
| void SetGlobalFlags(i::EnumSet<ParserFlag> flags) { |
| i::FLAG_allow_natives_syntax = flags.Contains(kAllowNatives); |
| i::FLAG_harmony_function_sent = flags.Contains(kAllowHarmonyFunctionSent); |
| i::FLAG_harmony_public_fields = flags.Contains(kAllowHarmonyPublicFields); |
| i::FLAG_harmony_private_fields = flags.Contains(kAllowHarmonyPrivateFields); |
| i::FLAG_harmony_static_fields = flags.Contains(kAllowHarmonyStaticFields); |
| i::FLAG_harmony_dynamic_import = flags.Contains(kAllowHarmonyDynamicImport); |
| i::FLAG_harmony_import_meta = flags.Contains(kAllowHarmonyImportMeta); |
| i::FLAG_harmony_do_expressions = flags.Contains(kAllowHarmonyDoExpressions); |
| i::FLAG_harmony_optional_catch_binding = |
| flags.Contains(kAllowHarmonyOptionalCatchBinding); |
| } |
| |
| void SetParserFlags(i::PreParser* parser, i::EnumSet<ParserFlag> flags) { |
| parser->set_allow_natives(flags.Contains(kAllowNatives)); |
| parser->set_allow_harmony_function_sent( |
| flags.Contains(kAllowHarmonyFunctionSent)); |
| parser->set_allow_harmony_public_fields( |
| flags.Contains(kAllowHarmonyPublicFields)); |
| parser->set_allow_harmony_private_fields( |
| flags.Contains(kAllowHarmonyPrivateFields)); |
| parser->set_allow_harmony_static_fields( |
| flags.Contains(kAllowHarmonyStaticFields)); |
| parser->set_allow_harmony_dynamic_import( |
| flags.Contains(kAllowHarmonyDynamicImport)); |
| parser->set_allow_harmony_import_meta( |
| flags.Contains(kAllowHarmonyImportMeta)); |
| parser->set_allow_harmony_do_expressions( |
| flags.Contains(kAllowHarmonyDoExpressions)); |
| parser->set_allow_harmony_optional_catch_binding( |
| flags.Contains(kAllowHarmonyOptionalCatchBinding)); |
| } |
| |
| void TestParserSyncWithFlags(i::Handle<i::String> source, |
| i::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(); |
| |
| uintptr_t stack_limit = isolate->stack_guard()->real_climit(); |
| |
| // Preparse the data. |
| i::PendingCompilationErrorHandler pending_error_handler; |
| if (test_preparser) { |
| i::Scanner scanner(isolate->unicode_cache()); |
| std::unique_ptr<i::Utf16CharacterStream> stream( |
| i::ScannerStream::For(source)); |
| i::Zone zone(CcTest::i_isolate()->allocator(), ZONE_NAME); |
| i::AstValueFactory ast_value_factory( |
| &zone, CcTest::i_isolate()->ast_string_constants(), |
| CcTest::i_isolate()->heap()->HashSeed()); |
| i::PreParser preparser(&zone, &scanner, stack_limit, &ast_value_factory, |
| &pending_error_handler, |
| isolate->counters()->runtime_call_stats(), |
| isolate->logger(), -1, is_module); |
| SetParserFlags(&preparser, flags); |
| scanner.Initialize(stream.get(), is_module); |
| i::PreParser::PreParseResult result = preparser.PreParseProgram(); |
| CHECK_EQ(i::PreParser::kPreParseSuccess, result); |
| } |
| |
| // Parse the data |
| i::FunctionLiteral* function; |
| { |
| i::Handle<i::Script> script = factory->NewScript(source); |
| i::ParseInfo info(script); |
| info.set_allow_lazy_parsing(flags.Contains(kAllowLazy)); |
| SetGlobalFlags(flags); |
| if (is_module) info.set_module(); |
| i::parsing::ParseProgram(&info, isolate); |
| 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())); |
| 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()) { |
| 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. |
| if (test_preparser && !ignore_error_msg) { |
| i::Handle<i::String> preparser_message = |
| pending_error_handler.FormatErrorMessageForTest(CcTest::i_isolate()); |
| if (!i::String::Equals(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()->NewStringFromAsciiChecked(source); |
| for (int bits = 0; bits < (1 << varying_flags_length); bits++) { |
| i::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 = i::StrLength(context_data[i][0]); |
| int kStatementLen = i::StrLength(statement_data[j]); |
| int kTerminationLen = i::StrLength(termination_data[k]); |
| int kSuffixLen = i::StrLength(context_data[i][1]); |
| int kProgramSize = kPrefixLen + kStatementLen + kTerminationLen |
| + kSuffixLen + i::StrLength("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.start(), 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"; |
| v8_compile(v8_str(script)); |
| 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 = i::StrLength(context_data[i][0]); |
| int kStatementLen = i::StrLength(statement_data[j]); |
| int kSuffixLen = i::StrLength(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]); |
| CHECK_EQ(length, kProgramSize); |
| TestParserSync(program.start(), 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(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, nullptr}}; |
| |
| const char* statement_data[] = {"(mylabel): while(true) { break mylabel; }", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(NoErrorsParenthesizedDirectivePrologue) { |
| // Parenthesized directive prologue shouldn't be recognized. |
| const char* context_data[][2] = {{"", ""}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"(\"use strict\"); var eval;", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(ErrorsNotAnIdentifierName) { |
| const char* context_data[][2] = { |
| {"", ""}, {"\"use strict\";", ""}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"var foo = {}; foo.{;", |
| "var foo = {}; foo.};", |
| "var foo = {}; foo.=;", |
| "var foo = {}; foo.888;", |
| "var foo = {}; foo.-;", |
| "var foo = {}; foo.--;", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(NoErrorsIdentifierNames) { |
| // Keywords etc. are valid as property names. |
| const char* context_data[][2] = { |
| {"", ""}, {"\"use strict\";", ""}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"var foo = {}; foo.if;", |
| "var foo = {}; foo.yield;", |
| "var foo = {}; foo.super;", |
| "var foo = {}; foo.interface;", |
| "var foo = {}; foo.eval;", |
| "var foo = {}; foo.arguments;", |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(DontRegressPreParserDataSizes) { |
| // These tests make sure that Parser doesn't start producing less "preparse |
| // data" (data which the embedder can cache). |
| |
| v8::V8::Initialize(); |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope handles(isolate); |
| |
| CcTest::i_isolate()->stack_guard()->SetStackLimit( |
| i::GetCurrentStackPosition() - 128 * 1024); |
| |
| struct TestCase { |
| const char* program; |
| int functions; |
| } test_cases[] = { |
| // No functions. |
| {"var x = 42;", 0}, |
| // Functions. |
| {"function foo() {}", 1}, |
| {"function foo() {} function bar() {}", 2}, |
| // Getter / setter functions are recorded as functions if they're on the |
| // top |
| // level. |
| {"var x = {get foo(){} };", 1}, |
| // Functions insize lazy functions are not recorded. |
| {"function lazy() { function a() {} function b() {} function c() {} }", |
| 1}, |
| {"function lazy() { var x = {get foo(){} } }", 1}, |
| {nullptr, 0}}; |
| |
| for (int i = 0; test_cases[i].program; i++) { |
| const char* program = test_cases[i].program; |
| i::Factory* factory = CcTest::i_isolate()->factory(); |
| i::Handle<i::String> source = |
| factory->NewStringFromUtf8(i::CStrVector(program)).ToHandleChecked(); |
| i::Handle<i::Script> script = factory->NewScript(source); |
| i::ParseInfo info(script); |
| i::ScriptData* sd = nullptr; |
| info.set_cached_data(&sd); |
| info.set_compile_options(v8::ScriptCompiler::kProduceParserCache); |
| i::parsing::ParseProgram(&info, CcTest::i_isolate()); |
| i::ParseData* pd = i::ParseData::FromCachedData(sd); |
| |
| if (pd->FunctionCount() != test_cases[i].functions) { |
| FATAL( |
| "Expected preparse data for program:\n" |
| "\t%s\n" |
| "to contain %d functions, however, received %d functions.\n", |
| program, test_cases[i].functions, pd->FunctionCount()); |
| } |
| delete sd; |
| delete pd; |
| } |
| } |
| |
| |
| TEST(FunctionDeclaresItselfStrict) { |
| // Tests that we produce the right kinds of errors when a function declares |
| // itself strict (we cannot produce there errors as soon as we see the |
| // offending identifiers, because we don't know at that point whether the |
| // function is strict or not). |
| const char* context_data[][2] = {{"function eval() {", "}"}, |
| {"function arguments() {", "}"}, |
| {"function yield() {", "}"}, |
| {"function interface() {", "}"}, |
| {"function foo(eval) {", "}"}, |
| {"function foo(arguments) {", "}"}, |
| {"function foo(yield) {", "}"}, |
| {"function foo(interface) {", "}"}, |
| {"function foo(bar, eval) {", "}"}, |
| {"function foo(bar, arguments) {", "}"}, |
| {"function foo(bar, yield) {", "}"}, |
| {"function foo(bar, interface) {", "}"}, |
| {"function foo(bar, bar) {", "}"}, |
| {nullptr, nullptr}}; |
| |
| const char* strict_statement_data[] = {"\"use strict\";", nullptr}; |
| |
| const char* non_strict_statement_data[] = {";", nullptr}; |
| |
| RunParserSyncTest(context_data, strict_statement_data, kError); |
| RunParserSyncTest(context_data, non_strict_statement_data, kSuccess); |
| } |
| |
| |
| TEST(ErrorsTryWithoutCatchOrFinally) { |
| const char* context_data[][2] = {{"", ""}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = { |
| "try { }", "try { } foo();", "try { } catch (e) foo();", |
| "try { } catch { }", "try { } finally foo();", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(NoErrorsTryCatchFinally) { |
| const char* context_data[][2] = {{"", ""}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"try { } catch (e) { }", |
| "try { } catch (e) { } finally { }", |
| "try { } finally { }", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| TEST(OptionalCatchBinding) { |
| // clang-format off |
| const char* context_data[][2] = { |
| {"", ""}, |
| {"'use strict';", ""}, |
| {"try {", "} catch (e) { }"}, |
| {"try {} catch (e) {", "}"}, |
| {"try {", "} catch ({e}) { }"}, |
| {"try {} catch ({e}) {", "}"}, |
| {"function f() {", "}"}, |
| { NULL, NULL } |
| }; |
| |
| const char* statement_data[] = { |
| "try { } catch { }", |
| "try { } catch { } finally { }", |
| "try { let e; } catch { let e; }", |
| "try { let e; } catch { let e; } finally { let e; }", |
| NULL |
| }; |
| // clang-format on |
| |
| // No error with flag |
| static const ParserFlag flags[] = {kAllowHarmonyOptionalCatchBinding}; |
| RunParserSyncTest(context_data, statement_data, kSuccess, NULL, 0, flags, |
| arraysize(flags)); |
| |
| // Still an error without flag |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| TEST(OptionalCatchBindingInDoExpression) { |
| // This is an edge case no otherwise hit: a catch scope in a parameter |
| // expression which needs its own scope. |
| // clang-format off |
| const char* context_data[][2] = { |
| {"((x = (eval(''), do {", "}))=>{})()"}, |
| { NULL, NULL } |
| }; |
| |
| const char* statement_data[] = { |
| "try { } catch { }", |
| "try { } catch { } finally { }", |
| "try { let e; } catch { let e; }", |
| "try { let e; } catch { let e; } finally { let e; }", |
| NULL |
| }; |
| // clang-format on |
| |
| // No error with flag |
| static const ParserFlag do_and_catch_flags[] = { |
| kAllowHarmonyDoExpressions, kAllowHarmonyOptionalCatchBinding}; |
| RunParserSyncTest(context_data, statement_data, kSuccess, NULL, 0, |
| do_and_catch_flags, arraysize(do_and_catch_flags)); |
| |
| // Still an error without flag |
| static const ParserFlag do_flag[] = {kAllowHarmonyDoExpressions}; |
| RunParserSyncTest(context_data, statement_data, kError, NULL, 0, do_flag, |
| arraysize(do_flag)); |
| } |
| |
| TEST(ErrorsRegexpLiteral) { |
| const char* context_data[][2] = {{"var r = ", ""}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"/unterminated", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(NoErrorsRegexpLiteral) { |
| const char* context_data[][2] = {{"var r = ", ""}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"/foo/", "/foo/g", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(NoErrorsNewExpression) { |
| const char* context_data[][2] = { |
| {"", ""}, {"var f =", ""}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = { |
| "new foo", "new foo();", "new foo(1);", "new foo(1, 2);", |
| // The first () will be processed as a part of the NewExpression and the |
| // second () will be processed as part of LeftHandSideExpression. |
| "new foo()();", |
| // The first () will be processed as a part of the inner NewExpression and |
| // the second () will be processed as a part of the outer NewExpression. |
| "new new foo()();", "new foo.bar;", "new foo.bar();", "new foo.bar.baz;", |
| "new foo.bar().baz;", "new foo[bar];", "new foo[bar]();", |
| "new foo[bar][baz];", "new foo[bar]()[baz];", |
| "new foo[bar].baz(baz)()[bar].baz;", |
| "new \"foo\"", // Runtime error |
| "new 1", // Runtime error |
| // This even runs: |
| "(new new Function(\"this.x = 1\")).x;", |
| "new new Test_Two(String, 2).v(0123).length;", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(ErrorsNewExpression) { |
| const char* context_data[][2] = { |
| {"", ""}, {"var f =", ""}, {nullptr, nullptr}}; |
| |
| const char* statement_data[] = {"new foo bar", "new ) foo", "new ++foo", |
| "new foo ++", nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(StrictObjectLiteralChecking) { |
| const char* context_data[][2] = {{"\"use strict\"; var myobject = {", "};"}, |
| {"\"use strict\"; var myobject = {", ",};"}, |
| {"var myobject = {", "};"}, |
| {"var myobject = {", ",};"}, |
| {nullptr, nullptr}}; |
| |
| // These are only errors in strict mode. |
| const char* statement_data[] = { |
| "foo: 1, foo: 2", "\"foo\": 1, \"foo\": 2", "foo: 1, \"foo\": 2", |
| "1: 1, 1: 2", "1: 1, \"1\": 2", |
| "get: 1, get: 2", // Not a getter for real, just a property called get. |
| "set: 1, set: 2", // Not a setter for real, just a property called set. |
| nullptr}; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(ErrorsObjectLiteralChecking) { |
| // clang-format off |
| const char* context_data[][2] = { |
| {"\"use strict\"; var myobject = {", "};"}, |
| {"var myobject = {", "};"}, |
| { nullptr, nullptr } |
| }; |
| |
| const char* statement_data[] = { |
| ",", |
| // Wrong number of parameters |
| "get bar(x) {}", |
| "get bar(x, y) {}", |
| "set bar() {}", |
| "set bar(x, y) {}", |
| // Parsing FunctionLiteral for getter or setter fails |
| "get foo( +", |
| "get foo() \"error\"", |
| // Various forbidden forms |
| "static x: 0", |
| "static x(){}", |
| "static async x(){}", |
| "static get x(){}", |
| "static get x : 0", |
| "static x", |
| "static 0", |
| "*x: 0", |
| "*x", |
| "*get x(){}", |
| "*set x(y){}", |
| "get *x(){}", |
| "set *x(y){}", |
| "get x*(){}", |
| "set x*(y){}", |
| "x = 0", |
| "* *x(){}", |
| "x*(){}", |
| "static async x(){}", |
| "static async x : 0", |
| "static async get x : 0", |
| "async static x(){}", |
| "*async x(){}", |
| "async x*(){}", |
| "async x : 0", |
| "async 0 : 0", |
| "async get x(){}", |
| "async get *x(){}", |
| "async set x(y){}", |
| "async get : 0", |
| nullptr |
| }; |
| // clang-format on |
| |
| RunParserSyncTest(context_data, statement_data, kError); |
| } |
| |
| |
| TEST(NoErrorsObjectLiteralChecking) { |
| // clang-format off |
| const char* context_data[][2] = { |
| {"var myobject = {", "};"}, |
| {"var myobject = {", ",};"}, |
| {"\"use strict\"; var myobject = {", "};"}, |
| {"\"use strict\"; var myobject = {", ",};"}, |
| { nullptr, nullptr } |
| }; |
| |
| const char* statement_data[] = { |
| "foo: 1, get foo() {}", |
| "foo: 1, set foo(v) {}", |
| "\"foo\": 1, get \"foo\"() {}", |
| "\"foo\": 1, set \"foo\"(v) {}", |
| "1: 1, get 1() {}", |
| "1: 1, set 1(v) {}", |
| "get foo() {}, get foo() {}", |
| "set foo(_) {}, set foo(v) {}", |
| "foo: 1, get \"foo\"() {}", |
| "foo: 1, set \"foo\"(v) {}", |
| "\"foo\": 1, get foo() {}", |
| "\"foo\": 1, set foo(v) {}", |
| "1: 1, get \"1\"() {}", |
| "1: 1, set \"1\"(v) {}", |
| "\"1\": 1, get 1() {}", |
| "\"1\": 1, set 1(v) {}", |
| "foo: 1, bar: 2", |
| "\"foo\": 1, \"bar\": 2", |
| "1: 1, 2: 2", |
| // Syntax: IdentifierName ':' AssignmentExpression |
| "foo: bar = 5 + baz", |
| // Syntax: 'get' PropertyName '(' ')' '{' FunctionBody '}' |
| "get foo() {}", |
| "get \"foo\"() {}", |
| "get 1() {}", |
| // Syntax: 'set' PropertyName '(' PropertySetParameterList ')' |
| // '{' FunctionBody '}' |
| "set foo(v) {}", |
| "set \"foo\"(v) {}", |
| "set 1(v) {}", |
| // Non-colliding getters and setters -> no errors |
| "foo: 1, get bar() {}", |
| "foo: 1, set bar(v) {}", |
| "\"foo\": 1, get \"bar\"() {}", |
| "\"foo\": 1, set \"bar\"(v) {}", |
| "1: 1, get 2() {}", |
| "1: 1, set 2(v) {}", |
| "get: 1, get foo() {}", |
| "set: 1, set foo(_) {}", |
| // Potentially confusing cases |
| "get(){}", |
| "set(){}", |
| "static(){}", |
| "async(){}", |
| "*get() {}", |
| "*set() {}", |
| "*static() {}", |
| "*async(){}", |
| "get : 0", |
| "set : 0", |
| "static : 0", |
| "async : 0", |
| // Keywords, future reserved and strict future reserved are also allowed as |
| // property names. |
| "if: 4", |
| "interface: 5", |
| "super: 6", |
| "eval: 7", |
| "arguments: 8", |
| "async x(){}", |
| "async 0(){}", |
| "async get(){}", |
| "async set(){}", |
| "async static(){}", |
| "async async(){}", |
| "async : 0", |
| "async(){}", |
| "*async(){}", |
| nullptr |
| }; |
| // clang-format on |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(TooManyArguments) { |
| const char* context_data[][2] = {{"foo(", "0)"}, {nullptr, nullptr}}; |
| |
| using v8::internal::Code; |
| char statement[Code::kMaxArguments * 2 + 1]; |
| for (int i = 0; i < Code::kMaxArguments; ++i) { |
| statement[2 * i] = '0'; |
| statement[2 * i + 1] = ','; |
| } |
| statement[Code::kMaxArguments * 2] = 0; |
| |
| const char* statement_data[] = {statement, nullptr}; |
| |
| // The test is quite slow, so run it with a reduced set of flags. |
| static const ParserFlag empty_flags[] = {kAllowLazy}; |
| RunParserSyncTest(context_data, statement_data, kError, empty_flags, 1); |
| } |
| |
| |
| TEST(StrictDelete) { |
| // "delete <Identifier>" is not allowed in strict mode. |
| const char* strict_context_data[][2] = {{"\"use strict\"; ", ""}, |
| {nullptr, nullptr}}; |
| |
| const char* sloppy_context_data[][2] = {{"", ""}, {nullptr, nullptr}}; |
| |
| // These are errors in the strict mode. |
| const char* sloppy_statement_data[] = {"delete foo;", "delete foo + 1;", |
| "delete (foo);", "delete eval;", |
| "delete interface;", nullptr}; |
| |
| // These are always OK |
| const char* good_statement_data[] = {"delete this;", |
| "delete 1;", |
| "delete 1 + 2;", |
| "delete foo();", |
| "delete foo.bar;", |
| "delete foo[bar];", |
| "delete foo--;", |
| "delete --foo;", |
| "delete new foo();", |
| "delete new foo(bar);", |
| nullptr}; |
| |
| // These are always errors |
| const char* bad_statement_data[] = {"delete if;", nullptr}; |
| |
| RunParserSyncTest(strict_context_data, sloppy_statement_data, kError); |
| RunParserSyncTest(sloppy_context_data, sloppy_statement_data, kSuccess); |
| |
| RunParserSyncTest(strict_context_data, good_statement_data, kSuccess); |
| RunParserSyncTest(sloppy_context_data, good_statement_data, kSuccess); |
| |
| RunParserSyncTest(strict_context_data, bad_statement_data, kError); |
| RunParserSyncTest(sloppy_context_data, bad_statement_data, kError); |
| } |
| |
| |
| TEST(NoErrorsDeclsInCase) { |
| const char* context_data[][2] = { |
| {"'use strict'; switch(x) { case 1:", "}"}, |
| {"function foo() {'use strict'; switch(x) { case 1:", "}}"}, |
| {"'use strict'; switch(x) { case 1: case 2:", "}"}, |
| {"function foo() {'use strict'; switch(x) { case 1: case 2:", "}}"}, |
| {"'use strict'; switch(x) { default:", "}"}, |
| {"function foo() {'use strict'; switch(x) { default:", "}}"}, |
| {"'use strict'; switch(x) { case 1: default:", "}"}, |
| {"function foo() {'use strict'; switch(x) { case 1: default:", "}}"}, |
| { nullptr, nullptr } |
| }; |
| |
| const char* statement_data[] = { |
| "function f() { }", |
| "class C { }", |
| "class C extends Q {}", |
| "function f() { } class C {}", |
| "function f() { }; class C {}", |
| "class C {}; function f() {}", |
| nullptr |
| }; |
| |
| RunParserSyncTest(context_data, statement_data, kSuccess); |
| } |
| |
| |
| TEST(InvalidLeftHandSide) { |
| const char* assignment_context_data[][2] = { |
| {"", " = 1;"}, {"\"use strict\"; ", " = 1;"}, {nullptr, nullptr}}; |
| |
|