| // 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<Utf16CharacterStream> stream; |
| std::unique_ptr<AsmJsScanner> 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 |