blob: 52a7d3ff7a9b4f33887fe6131265e2e015c64865 [file] [log] [blame]
// 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}};