// Copyright 2017 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/asmjs/asm-scanner.h" #include "src/objects/objects.h" #include "src/parsing/scanner-character-streams.h" #include "src/parsing/scanner.h" #include "testing/gtest/include/gtest/gtest.h" namespace v8 { namespace internal { #define TOK(t) AsmJsScanner::kToken_##t class AsmJsScannerTest : public ::testing::Test { protected: void SetupScanner(const char* source) { stream = ScannerStream::ForTesting(source); scanner.reset(new AsmJsScanner(stream.get())); } void Skip(AsmJsScanner::token_t t) { CHECK_EQ(t, scanner->Token()); scanner->Next(); } void SkipGlobal() { CHECK(scanner->IsGlobal()); scanner->Next(); } void SkipLocal() { CHECK(scanner->IsLocal()); scanner->Next(); } void CheckForEnd() { CHECK_EQ(scanner->Token(), AsmJsScanner::kEndOfInput); } void CheckForParseError() { CHECK_EQ(scanner->Token(), AsmJsScanner::kParseError); } std::unique_ptr stream; std::unique_ptr scanner; }; TEST_F(AsmJsScannerTest, SimpleFunction) { SetupScanner("function foo() { return; }"); Skip(TOK(function)); DCHECK_EQ("foo", scanner->GetIdentifierString()); SkipGlobal(); Skip('('); Skip(')'); Skip('{'); // clang-format off Skip(TOK(return)); // clang-format on Skip(';'); Skip('}'); CheckForEnd(); } TEST_F(AsmJsScannerTest, JSKeywords) { SetupScanner( "arguments break case const continue\n" "default do else eval for function\n" "if new return switch var while\n"); Skip(TOK(arguments)); Skip(TOK(break)); Skip(TOK(case)); Skip(TOK(const)); Skip(TOK(continue)); Skip(TOK(default)); Skip(TOK(do)); Skip(TOK(else)); Skip(TOK(eval)); Skip(TOK(for)); Skip(TOK(function)); Skip(TOK(if)); Skip(TOK(new)); // clang-format off Skip(TOK(return)); // clang-format on Skip(TOK(switch)); Skip(TOK(var)); Skip(TOK(while)); CheckForEnd(); } TEST_F(AsmJsScannerTest, JSOperatorsSpread) { SetupScanner( "+ - * / % & | ^ ~ << >> >>>\n" "< > <= >= == !=\n"); Skip('+'); Skip('-'); Skip('*'); Skip('/'); Skip('%'); Skip('&'); Skip('|'); Skip('^'); Skip('~'); Skip(TOK(SHL)); Skip(TOK(SAR)); Skip(TOK(SHR)); Skip('<'); Skip('>'); Skip(TOK(LE)); Skip(TOK(GE)); Skip(TOK(EQ)); Skip(TOK(NE)); CheckForEnd(); } TEST_F(AsmJsScannerTest, JSOperatorsTight) { SetupScanner( "+-*/%&|^~<<>> >>>\n" "<><=>= ==!=\n"); Skip('+'); Skip('-'); Skip('*'); Skip('/'); Skip('%'); Skip('&'); Skip('|'); Skip('^'); Skip('~'); Skip(TOK(SHL)); Skip(TOK(SAR)); Skip(TOK(SHR)); Skip('<'); Skip('>'); Skip(TOK(LE)); Skip(TOK(GE)); Skip(TOK(EQ)); Skip(TOK(NE)); CheckForEnd(); } TEST_F(AsmJsScannerTest, UsesOfAsm) { SetupScanner("'use asm' \"use asm\"\n"); Skip(TOK(UseAsm)); Skip(TOK(UseAsm)); CheckForEnd(); } TEST_F(AsmJsScannerTest, DefaultGlobalScope) { SetupScanner("var x = x + x;"); Skip(TOK(var)); CHECK_EQ("x", scanner->GetIdentifierString()); AsmJsScanner::token_t x = scanner->Token(); SkipGlobal(); Skip('='); Skip(x); Skip('+'); Skip(x); Skip(';'); CheckForEnd(); } TEST_F(AsmJsScannerTest, GlobalScope) { SetupScanner("var x = x + x;"); scanner->EnterGlobalScope(); Skip(TOK(var)); CHECK_EQ("x", scanner->GetIdentifierString()); AsmJsScanner::token_t x = scanner->Token(); SkipGlobal(); Skip('='); Skip(x); Skip('+'); Skip(x); Skip(';'); CheckForEnd(); } TEST_F(AsmJsScannerTest, LocalScope) { SetupScanner("var x = x + x;"); scanner->EnterLocalScope(); Skip(TOK(var)); CHECK_EQ("x", scanner->GetIdentifierString()); AsmJsScanner::token_t x = scanner->Token(); SkipLocal(); Skip('='); Skip(x); Skip('+'); Skip(x); Skip(';'); CheckForEnd(); } TEST_F(AsmJsScannerTest, Numbers) { SetupScanner("1 1.2 0x1F 1.e3"); CHECK(scanner->IsUnsigned()); CHECK_EQ(1, scanner->AsUnsigned()); scanner->Next(); CHECK(scanner->IsDouble()); CHECK_EQ(1.2, scanner->AsDouble()); scanner->Next(); CHECK(scanner->IsUnsigned()); CHECK_EQ(31, scanner->AsUnsigned()); scanner->Next(); CHECK(scanner->IsDouble()); CHECK_EQ(1.0e3, scanner->AsDouble()); scanner->Next(); CheckForEnd(); } TEST_F(AsmJsScannerTest, UnsignedNumbers) { SetupScanner("0x7FFFFFFF 0x80000000 0xFFFFFFFF 0x100000000"); CHECK(scanner->IsUnsigned()); CHECK_EQ(0x7FFFFFFF, scanner->AsUnsigned()); scanner->Next(); CHECK(scanner->IsUnsigned()); CHECK_EQ(0x80000000, scanner->AsUnsigned()); scanner->Next(); CHECK(scanner->IsUnsigned()); CHECK_EQ(0xFFFFFFFF, scanner->AsUnsigned()); scanner->Next(); // Numeric "unsigned" literals with a payload of more than 32-bit are rejected // by asm.js in all contexts, we hence consider `0x100000000` to be an error. CheckForParseError(); } TEST_F(AsmJsScannerTest, BadNumber) { SetupScanner(".123fe"); Skip('.'); CheckForParseError(); } TEST_F(AsmJsScannerTest, Rewind1) { SetupScanner("+ - * /"); Skip('+'); scanner->Rewind(); Skip('+'); Skip('-'); scanner->Rewind(); Skip('-'); Skip('*'); scanner->Rewind(); Skip('*'); Skip('/'); scanner->Rewind(); Skip('/'); CheckForEnd(); } TEST_F(AsmJsScannerTest, Comments) { SetupScanner( "var // This is a test /* */ eval\n" "var /* test *** test */ eval\n" "function /* this */ ^"); Skip(TOK(var)); Skip(TOK(var)); Skip(TOK(eval)); Skip(TOK(function)); Skip('^'); CheckForEnd(); } TEST_F(AsmJsScannerTest, TrailingCComment) { SetupScanner("var /* test\n"); Skip(TOK(var)); CheckForParseError(); } TEST_F(AsmJsScannerTest, Seeking) { SetupScanner("var eval do arguments function break\n"); Skip(TOK(var)); size_t old_pos = scanner->Position(); Skip(TOK(eval)); Skip(TOK(do)); Skip(TOK(arguments)); scanner->Rewind(); Skip(TOK(arguments)); scanner->Rewind(); scanner->Seek(old_pos); Skip(TOK(eval)); Skip(TOK(do)); Skip(TOK(arguments)); Skip(TOK(function)); Skip(TOK(break)); CheckForEnd(); } TEST_F(AsmJsScannerTest, Newlines) { SetupScanner( "var x = 1\n" "var y = 2\n"); Skip(TOK(var)); scanner->Next(); Skip('='); scanner->Next(); CHECK(scanner->IsPrecededByNewline()); Skip(TOK(var)); scanner->Next(); Skip('='); scanner->Next(); CHECK(scanner->IsPrecededByNewline()); CheckForEnd(); } } // namespace internal } // namespace v8