/// Copyright 2012 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 #include #include #include #include #include #include #ifdef ENABLE_VTUNE_JIT_INTERFACE #include "src/third_party/vtune/v8-vtune.h" #endif #include "src/d8-console.h" #include "src/d8.h" #include "src/ostreams.h" #include "include/libplatform/libplatform.h" #include "include/libplatform/v8-tracing.h" #include "include/v8-inspector.h" #include "src/api-inl.h" #include "src/base/cpu.h" #include "src/base/logging.h" #include "src/base/platform/platform.h" #include "src/base/platform/time.h" #include "src/base/sys-info.h" #include "src/basic-block-profiler.h" #include "src/debug/debug-interface.h" #include "src/interpreter/interpreter.h" #include "src/msan.h" #include "src/objects-inl.h" #include "src/objects.h" #include "src/snapshot/natives.h" #include "src/trap-handler/trap-handler.h" #include "src/utils.h" #include "src/v8.h" #include "src/wasm/wasm-engine.h" #if !defined(_WIN32) && !defined(_WIN64) #include // NOLINT #else #include // NOLINT #endif // !defined(_WIN32) && !defined(_WIN64) #ifndef DCHECK #define DCHECK(condition) assert(condition) #endif #ifndef CHECK #define CHECK(condition) assert(condition) #endif namespace v8 { namespace { const int kMB = 1024 * 1024; const int kMaxWorkers = 100; const int kMaxSerializerMemoryUsage = 1 * kMB; // Arbitrary maximum for testing. // Base class for shell ArrayBuffer allocators. It forwards all opertions to // the default v8 allocator. class ArrayBufferAllocatorBase : public v8::ArrayBuffer::Allocator { public: void* Allocate(size_t length) override { return allocator_->Allocate(length); } void* AllocateUninitialized(size_t length) override { return allocator_->AllocateUninitialized(length); } void Free(void* data, size_t length) override { allocator_->Free(data, length); } private: std::unique_ptr allocator_ = std::unique_ptr(NewDefaultAllocator()); }; // ArrayBuffer allocator that can use virtual memory to improve performance. class ShellArrayBufferAllocator : public ArrayBufferAllocatorBase { public: void* Allocate(size_t length) override { if (length >= kVMThreshold) return AllocateVM(length); return ArrayBufferAllocatorBase::Allocate(length); } void* AllocateUninitialized(size_t length) override { if (length >= kVMThreshold) return AllocateVM(length); return ArrayBufferAllocatorBase::AllocateUninitialized(length); } void Free(void* data, size_t length) override { if (length >= kVMThreshold) { FreeVM(data, length); } else { ArrayBufferAllocatorBase::Free(data, length); } } private: static constexpr size_t kVMThreshold = 65536; static constexpr size_t kTwoGB = 2u * 1024u * 1024u * 1024u; void* AllocateVM(size_t length) { DCHECK_LE(kVMThreshold, length); // TODO(titzer): allocations should fail if >= 2gb because array buffers // store their lengths as a SMI internally. if (length >= kTwoGB) return nullptr; v8::PageAllocator* page_allocator = i::GetPlatformPageAllocator(); size_t page_size = page_allocator->AllocatePageSize(); size_t allocated = RoundUp(length, page_size); // Rounding up could go over the limit. if (allocated >= kTwoGB) return nullptr; return i::AllocatePages(page_allocator, nullptr, allocated, page_size, PageAllocator::kReadWrite); } void FreeVM(void* data, size_t length) { v8::PageAllocator* page_allocator = i::GetPlatformPageAllocator(); size_t page_size = page_allocator->AllocatePageSize(); size_t allocated = RoundUp(length, page_size); CHECK(i::FreePages(page_allocator, data, allocated)); } }; // ArrayBuffer allocator that never allocates over 10MB. class MockArrayBufferAllocator : public ArrayBufferAllocatorBase { protected: void* Allocate(size_t length) override { return ArrayBufferAllocatorBase::Allocate(Adjust(length)); } void* AllocateUninitialized(size_t length) override { return ArrayBufferAllocatorBase::AllocateUninitialized(Adjust(length)); } void Free(void* data, size_t length) override { return ArrayBufferAllocatorBase::Free(data, Adjust(length)); } private: size_t Adjust(size_t length) { const size_t kAllocationLimit = 10 * kMB; return length > kAllocationLimit ? i::AllocatePageSize() : length; } }; // ArrayBuffer allocator that can be equipped with a limit to simulate system // OOM. class MockArrayBufferAllocatiorWithLimit : public MockArrayBufferAllocator { public: explicit MockArrayBufferAllocatiorWithLimit(size_t allocation_limit) : space_left_(allocation_limit) {} protected: void* Allocate(size_t length) override { if (length > space_left_) { return nullptr; } space_left_ -= length; return MockArrayBufferAllocator::Allocate(length); } void* AllocateUninitialized(size_t length) override { if (length > space_left_) { return nullptr; } space_left_ -= length; return MockArrayBufferAllocator::AllocateUninitialized(length); } void Free(void* data, size_t length) override { space_left_ += length; return MockArrayBufferAllocator::Free(data, length); } private: std::atomic space_left_; }; // Predictable v8::Platform implementation. Worker threads are disabled, idle // tasks are disallowed, and the time reported by {MonotonicallyIncreasingTime} // is deterministic. class PredictablePlatform : public Platform { public: explicit PredictablePlatform(std::unique_ptr platform) : platform_(std::move(platform)) { DCHECK_NOT_NULL(platform_); } PageAllocator* GetPageAllocator() override { return platform_->GetPageAllocator(); } void OnCriticalMemoryPressure() override { platform_->OnCriticalMemoryPressure(); } bool OnCriticalMemoryPressure(size_t length) override { return platform_->OnCriticalMemoryPressure(length); } std::shared_ptr GetForegroundTaskRunner( v8::Isolate* isolate) override { return platform_->GetForegroundTaskRunner(isolate); } int NumberOfWorkerThreads() override { return 0; } void CallOnWorkerThread(std::unique_ptr task) override { // It's not defined when background tasks are being executed, so we can just // execute them right away. task->Run(); } void CallDelayedOnWorkerThread(std::unique_ptr task, double delay_in_seconds) override { // Never run delayed tasks. } void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override { // This is a deprecated function and should not be called anymore. UNREACHABLE(); } void CallDelayedOnForegroundThread(v8::Isolate* isolate, Task* task, double delay_in_seconds) override { // This is a deprecated function and should not be called anymore. UNREACHABLE(); } void CallIdleOnForegroundThread(Isolate* isolate, IdleTask* task) override { UNREACHABLE(); } bool IdleTasksEnabled(Isolate* isolate) override { return false; } double MonotonicallyIncreasingTime() override { return synthetic_time_in_sec_ += 0.00001; } double CurrentClockTimeMillis() override { return MonotonicallyIncreasingTime() * base::Time::kMillisecondsPerSecond; } v8::TracingController* GetTracingController() override { return platform_->GetTracingController(); } Platform* platform() const { return platform_.get(); } private: double synthetic_time_in_sec_ = 0.0; std::unique_ptr platform_; DISALLOW_COPY_AND_ASSIGN(PredictablePlatform); }; std::unique_ptr g_platform; v8::Platform* GetDefaultPlatform() { return i::FLAG_verify_predictable ? static_cast(g_platform.get())->platform() : g_platform.get(); } static Local Throw(Isolate* isolate, const char* message) { return isolate->ThrowException( String::NewFromUtf8(isolate, message, NewStringType::kNormal) .ToLocalChecked()); } static Local GetValue(v8::Isolate* isolate, Local context, Local object, const char* property) { Local v8_str = String::NewFromUtf8(isolate, property, NewStringType::kNormal) .ToLocalChecked(); return object->Get(context, v8_str).ToLocalChecked(); } Worker* GetWorkerFromInternalField(Isolate* isolate, Local object) { if (object->InternalFieldCount() != 1) { Throw(isolate, "this is not a Worker"); return nullptr; } Worker* worker = static_cast(object->GetAlignedPointerFromInternalField(0)); if (worker == nullptr) { Throw(isolate, "Worker is defunct because main thread is terminating"); return nullptr; } return worker; } base::Thread::Options GetThreadOptions(const char* name) { // On some systems (OSX 10.6) the stack size default is 0.5Mb or less // which is not enough to parse the big literal expressions used in tests. // The stack size should be at least StackGuard::kLimitSize + some // OS-specific padding for thread startup code. 2Mbytes seems to be enough. return base::Thread::Options(name, 2 * kMB); } } // namespace namespace tracing { namespace { // String options that can be used to initialize TraceOptions. const char kRecordUntilFull[] = "record-until-full"; const char kRecordContinuously[] = "record-continuously"; const char kRecordAsMuchAsPossible[] = "record-as-much-as-possible"; const char kRecordModeParam[] = "record_mode"; const char kEnableSystraceParam[] = "enable_systrace"; const char kEnableArgumentFilterParam[] = "enable_argument_filter"; const char kIncludedCategoriesParam[] = "included_categories"; class TraceConfigParser { public: static void FillTraceConfig(v8::Isolate* isolate, platform::tracing::TraceConfig* trace_config, const char* json_str) { HandleScope outer_scope(isolate); Local context = Context::New(isolate); Context::Scope context_scope(context); HandleScope inner_scope(isolate); Local source = String::NewFromUtf8(isolate, json_str, NewStringType::kNormal) .ToLocalChecked(); Local result = JSON::Parse(context, source).ToLocalChecked(); Local trace_config_object = Local::Cast(result); trace_config->SetTraceRecordMode( GetTraceRecordMode(isolate, context, trace_config_object)); if (GetBoolean(isolate, context, trace_config_object, kEnableSystraceParam)) { trace_config->EnableSystrace(); } if (GetBoolean(isolate, context, trace_config_object, kEnableArgumentFilterParam)) { trace_config->EnableArgumentFilter(); } UpdateIncludedCategoriesList(isolate, context, trace_config_object, trace_config); } private: static bool GetBoolean(v8::Isolate* isolate, Local context, Local object, const char* property) { Local value = GetValue(isolate, context, object, property); if (value->IsNumber()) { return value->BooleanValue(isolate); } return false; } static int UpdateIncludedCategoriesList( v8::Isolate* isolate, Local context, Local object, platform::tracing::TraceConfig* trace_config) { Local value = GetValue(isolate, context, object, kIncludedCategoriesParam); if (value->IsArray()) { Local v8_array = Local::Cast(value); for (int i = 0, length = v8_array->Length(); i < length; ++i) { Local v = v8_array->Get(context, i) .ToLocalChecked() ->ToString(context) .ToLocalChecked(); String::Utf8Value str(isolate, v->ToString(context).ToLocalChecked()); trace_config->AddIncludedCategory(*str); } return v8_array->Length(); } return 0; } static platform::tracing::TraceRecordMode GetTraceRecordMode( v8::Isolate* isolate, Local context, Local object) { Local value = GetValue(isolate, context, object, kRecordModeParam); if (value->IsString()) { Local v8_string = value->ToString(context).ToLocalChecked(); String::Utf8Value str(isolate, v8_string); if (strcmp(kRecordUntilFull, *str) == 0) { return platform::tracing::TraceRecordMode::RECORD_UNTIL_FULL; } else if (strcmp(kRecordContinuously, *str) == 0) { return platform::tracing::TraceRecordMode::RECORD_CONTINUOUSLY; } else if (strcmp(kRecordAsMuchAsPossible, *str) == 0) { return platform::tracing::TraceRecordMode::RECORD_AS_MUCH_AS_POSSIBLE; } } return platform::tracing::TraceRecordMode::RECORD_UNTIL_FULL; } }; } // namespace static platform::tracing::TraceConfig* CreateTraceConfigFromJSON( v8::Isolate* isolate, const char* json_str) { platform::tracing::TraceConfig* trace_config = new platform::tracing::TraceConfig(); TraceConfigParser::FillTraceConfig(isolate, trace_config, json_str); return trace_config; } } // namespace tracing class ExternalOwningOneByteStringResource : public String::ExternalOneByteStringResource { public: ExternalOwningOneByteStringResource() : length_(0) {} ExternalOwningOneByteStringResource(std::unique_ptr data, size_t length) : data_(std::move(data)), length_(length) {} const char* data() const override { return data_.get(); } size_t length() const override { return length_; } private: std::unique_ptr data_; size_t length_; }; CounterMap* Shell::counter_map_; base::OS::MemoryMappedFile* Shell::counters_file_ = nullptr; CounterCollection Shell::local_counters_; CounterCollection* Shell::counters_ = &local_counters_; base::LazyMutex Shell::context_mutex_; const base::TimeTicks Shell::kInitialTicks = base::TimeTicks::HighResolutionNow(); Global Shell::stringify_function_; base::LazyMutex Shell::workers_mutex_; bool Shell::allow_new_workers_ = true; std::vector Shell::workers_; std::vector Shell::externalized_contents_; std::atomic Shell::script_executed_{false}; base::LazyMutex Shell::isolate_status_lock_; std::map Shell::isolate_status_; base::LazyMutex Shell::cached_code_mutex_; std::map> Shell::cached_code_map_; Global Shell::evaluation_context_; ArrayBuffer::Allocator* Shell::array_buffer_allocator; ShellOptions Shell::options; base::OnceType Shell::quit_once_ = V8_ONCE_INIT; // Dummy external source stream which returns the whole source in one go. class DummySourceStream : public v8::ScriptCompiler::ExternalSourceStream { public: DummySourceStream(Local source, Isolate* isolate) : done_(false) { source_length_ = source->Utf8Length(isolate); source_buffer_.reset(new uint8_t[source_length_]); source->WriteUtf8(isolate, reinterpret_cast(source_buffer_.get()), source_length_); } size_t GetMoreData(const uint8_t** src) override { if (done_) { return 0; } *src = source_buffer_.release(); done_ = true; return source_length_; } private: int source_length_; std::unique_ptr source_buffer_; bool done_; }; class BackgroundCompileThread : public base::Thread { public: BackgroundCompileThread(Isolate* isolate, Local source) : base::Thread(GetThreadOptions("BackgroundCompileThread")), source_(source), streamed_source_(new DummySourceStream(source, isolate), v8::ScriptCompiler::StreamedSource::UTF8), task_(v8::ScriptCompiler::StartStreamingScript(isolate, &streamed_source_)) {} void Run() override { task_->Run(); } v8::ScriptCompiler::StreamedSource* streamed_source() { return &streamed_source_; } private: Local source_; v8::ScriptCompiler::StreamedSource streamed_source_; std::unique_ptr task_; }; ScriptCompiler::CachedData* Shell::LookupCodeCache(Isolate* isolate, Local source) { base::LockGuard lock_guard(cached_code_mutex_.Pointer()); CHECK(source->IsString()); v8::String::Utf8Value key(isolate, source); DCHECK(*key); auto entry = cached_code_map_.find(*key); if (entry != cached_code_map_.end() && entry->second) { int length = entry->second->length; uint8_t* cache = new uint8_t[length]; memcpy(cache, entry->second->data, length); ScriptCompiler::CachedData* cached_data = new ScriptCompiler::CachedData( cache, length, ScriptCompiler::CachedData::BufferOwned); return cached_data; } return nullptr; } void Shell::StoreInCodeCache(Isolate* isolate, Local source, const ScriptCompiler::CachedData* cache_data) { base::LockGuard lock_guard(cached_code_mutex_.Pointer()); CHECK(source->IsString()); if (cache_data == nullptr) return; v8::String::Utf8Value key(isolate, source); DCHECK(*key); int length = cache_data->length; uint8_t* cache = new uint8_t[length]; memcpy(cache, cache_data->data, length); cached_code_map_[*key] = std::unique_ptr( new ScriptCompiler::CachedData(cache, length, ScriptCompiler::CachedData::BufferOwned)); } // Executes a string within the current v8 context. bool Shell::ExecuteString(Isolate* isolate, Local source, Local name, PrintResult print_result, ReportExceptions report_exceptions, ProcessMessageQueue process_message_queue) { HandleScope handle_scope(isolate); TryCatch try_catch(isolate); try_catch.SetVerbose(true); MaybeLocal maybe_result; bool success = true; { PerIsolateData* data = PerIsolateData::Get(isolate); Local realm = Local::New(isolate, data->realms_[data->realm_current_]); Context::Scope context_scope(realm); MaybeLocal