// Copyright 2016 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 #include "include/v8.h" #include "src/api-inl.h" #include "src/ast/ast.h" #include "src/ast/scopes.h" #include "src/base/platform/semaphore.h" #include "src/base/template-utils.h" #include "src/compiler-dispatcher/compiler-dispatcher-job.h" #include "src/compiler-dispatcher/compiler-dispatcher-tracer.h" #include "src/compiler-dispatcher/unoptimized-compile-job.h" #include "src/flags.h" #include "src/isolate-inl.h" #include "src/parsing/parse-info.h" #include "src/v8.h" #include "test/unittests/test-helpers.h" #include "test/unittests/test-utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace v8 { namespace internal { class UnoptimizedCompileJobTest : public TestWithNativeContext { public: UnoptimizedCompileJobTest() : tracer_(isolate()) {} ~UnoptimizedCompileJobTest() override {} CompilerDispatcherTracer* tracer() { return &tracer_; } static void SetUpTestCase() { CHECK_NULL(save_flags_); save_flags_ = new SaveFlags(); TestWithNativeContext ::SetUpTestCase(); } static void TearDownTestCase() { TestWithNativeContext ::TearDownTestCase(); CHECK_NOT_NULL(save_flags_); delete save_flags_; save_flags_ = nullptr; } static Variable* LookupVariableByName(UnoptimizedCompileJob* job, const char* name) { const AstRawString* name_raw_string = job->parse_info_->ast_value_factory()->GetOneByteString(name); return job->parse_info_->literal()->scope()->Lookup(name_raw_string); } private: CompilerDispatcherTracer tracer_; static SaveFlags* save_flags_; DISALLOW_COPY_AND_ASSIGN(UnoptimizedCompileJobTest); }; SaveFlags* UnoptimizedCompileJobTest::save_flags_ = nullptr; #define ASSERT_JOB_STATUS(STATUS, JOB) ASSERT_EQ(STATUS, JOB->status()) TEST_F(UnoptimizedCompileJobTest, Construct) { std::unique_ptr job(new UnoptimizedCompileJob( isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), nullptr), FLAG_stack_size)); } TEST_F(UnoptimizedCompileJobTest, StateTransitions) { std::unique_ptr job(new UnoptimizedCompileJob( isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), nullptr), FLAG_stack_size)); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job); job->PrepareOnMainThread(isolate()); ASSERT_FALSE(job->IsFailed()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kPrepared, job); job->Compile(false); ASSERT_FALSE(job->IsFailed()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kCompiled, job); job->FinalizeOnMainThread(isolate()); ASSERT_FALSE(job->IsFailed()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job); job->ResetOnMainThread(isolate()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job); } TEST_F(UnoptimizedCompileJobTest, SyntaxError) { test::ScriptResource* script = new test::ScriptResource("^^^", strlen("^^^")); std::unique_ptr job(new UnoptimizedCompileJob( isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), script), FLAG_stack_size)); job->PrepareOnMainThread(isolate()); ASSERT_FALSE(job->IsFailed()); job->Compile(false); ASSERT_FALSE(job->IsFailed()); job->ReportErrorsOnMainThread(isolate()); ASSERT_TRUE(job->IsFailed()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job); ASSERT_TRUE(isolate()->has_pending_exception()); isolate()->clear_pending_exception(); job->ResetOnMainThread(isolate()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job); } TEST_F(UnoptimizedCompileJobTest, CompileAndRun) { const char script[] = "function g() {\n" " f = function(a) {\n" " for (var i = 0; i < 3; i++) { a += 20; }\n" " return a;\n" " }\n" " return f;\n" "}\n" "g();"; Handle f = RunJS(script); std::unique_ptr job(new UnoptimizedCompileJob( isolate(), tracer(), handle(f->shared(), f->GetIsolate()), FLAG_stack_size)); job->PrepareOnMainThread(isolate()); ASSERT_FALSE(job->IsFailed()); job->Compile(false); ASSERT_FALSE(job->IsFailed()); job->FinalizeOnMainThread(isolate()); ASSERT_FALSE(job->IsFailed()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job); Smi* value = Smi::cast(*RunJS("f(100);")); ASSERT_TRUE(value == Smi::FromInt(160)); job->ResetOnMainThread(isolate()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job); } TEST_F(UnoptimizedCompileJobTest, CompileFailureToAnalyse) { std::string raw_script("() { var a = "); for (int i = 0; i < 500000; i++) { // TODO(leszeks): Figure out a more "unit-test-y" way of forcing an analysis // failure than a binop stack overflow. // Alternate + and - to avoid n-ary operation nodes. raw_script += "'x' + 'x' - "; } raw_script += " 'x'; }"; test::ScriptResource* script = new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str())); std::unique_ptr job(new UnoptimizedCompileJob( isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), script), 100)); job->PrepareOnMainThread(isolate()); ASSERT_FALSE(job->IsFailed()); job->Compile(false); ASSERT_FALSE(job->IsFailed()); job->ReportErrorsOnMainThread(isolate()); ASSERT_TRUE(job->IsFailed()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job); ASSERT_TRUE(isolate()->has_pending_exception()); isolate()->clear_pending_exception(); job->ResetOnMainThread(isolate()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job); } TEST_F(UnoptimizedCompileJobTest, CompileFailureToFinalize) { std::string raw_script("() { var a = "); for (int i = 0; i < 500; i++) { // Alternate + and - to avoid n-ary operation nodes. raw_script += "'x' + 'x' - "; } raw_script += " 'x'; }"; test::ScriptResource* script = new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str())); std::unique_ptr job(new UnoptimizedCompileJob( isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), script), 50)); job->PrepareOnMainThread(isolate()); ASSERT_FALSE(job->IsFailed()); job->Compile(false); ASSERT_FALSE(job->IsFailed()); job->ReportErrorsOnMainThread(isolate()); ASSERT_TRUE(job->IsFailed()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job); ASSERT_TRUE(isolate()->has_pending_exception()); isolate()->clear_pending_exception(); job->ResetOnMainThread(isolate()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job); } class CompileTask : public Task { public: CompileTask(UnoptimizedCompileJob* job, base::Semaphore* semaphore) : job_(job), semaphore_(semaphore) {} ~CompileTask() override {} void Run() override { job_->Compile(true); ASSERT_FALSE(job_->IsFailed()); semaphore_->Signal(); } private: UnoptimizedCompileJob* job_; base::Semaphore* semaphore_; DISALLOW_COPY_AND_ASSIGN(CompileTask); }; TEST_F(UnoptimizedCompileJobTest, CompileOnBackgroundThread) { const char* raw_script = "(a, b) {\n" " var c = a + b;\n" " function bar() { return b }\n" " var d = { foo: 100, bar : bar() }\n" " return bar;" "}"; test::ScriptResource* script = new test::ScriptResource(raw_script, strlen(raw_script)); std::unique_ptr job(new UnoptimizedCompileJob( isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), script), 100)); job->PrepareOnMainThread(isolate()); ASSERT_FALSE(job->IsFailed()); base::Semaphore semaphore(0); auto background_task = base::make_unique(job.get(), &semaphore); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kPrepared, job); V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(background_task)); semaphore.Wait(); job->FinalizeOnMainThread(isolate()); ASSERT_FALSE(job->IsFailed()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job); job->ResetOnMainThread(isolate()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job); } TEST_F(UnoptimizedCompileJobTest, LazyInnerFunctions) { const char script[] = "f = function() {\n" " e = (function() { return 42; });\n" " return e;\n" "};\n" "f;"; Handle f = RunJS(script); std::unique_ptr job(new UnoptimizedCompileJob( isolate(), tracer(), handle(f->shared(), f->GetIsolate()), FLAG_stack_size)); job->PrepareOnMainThread(isolate()); ASSERT_FALSE(job->IsFailed()); job->Compile(false); ASSERT_FALSE(job->IsFailed()); job->FinalizeOnMainThread(isolate()); ASSERT_FALSE(job->IsFailed()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job); Handle e = RunJS("f();"); ASSERT_FALSE(e->shared()->is_compiled()); job->ResetOnMainThread(isolate()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job); } #undef ASSERT_JOB_STATUS } // namespace internal } // namespace v8