| // 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 <memory> | 
 |  | 
 | #include "include/v8.h" | 
 | #include "src/api.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<UnoptimizedCompileJob> job(new UnoptimizedCompileJob( | 
 |       isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), nullptr), | 
 |       FLAG_stack_size)); | 
 | } | 
 |  | 
 | TEST_F(UnoptimizedCompileJobTest, StateTransitions) { | 
 |   std::unique_ptr<UnoptimizedCompileJob> 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("^^^", strlen("^^^")); | 
 |   std::unique_ptr<UnoptimizedCompileJob> 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<JSFunction> f = RunJS<JSFunction>(script); | 
 |   std::unique_ptr<UnoptimizedCompileJob> job(new UnoptimizedCompileJob( | 
 |       isolate(), tracer(), handle(f->shared()), 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(raw_script.c_str(), strlen(raw_script.c_str())); | 
 |   std::unique_ptr<UnoptimizedCompileJob> 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(raw_script.c_str(), strlen(raw_script.c_str())); | 
 |   std::unique_ptr<UnoptimizedCompileJob> 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(raw_script, strlen(raw_script)); | 
 |   std::unique_ptr<UnoptimizedCompileJob> 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<CompileTask>(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<JSFunction> f = RunJS<JSFunction>(script); | 
 |  | 
 |   std::unique_ptr<UnoptimizedCompileJob> job(new UnoptimizedCompileJob( | 
 |       isolate(), tracer(), handle(f->shared()), 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<JSFunction> e = RunJS<JSFunction>("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 |