// 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 #include #ifdef ENABLE_VTUNE_JIT_INTERFACE #include "src/third_party/vtune/v8-vtune.h" #endif #include "include/libplatform/libplatform.h" #include "include/libplatform/v8-tracing.h" #include "include/v8-inspector.h" #include "src/api/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/d8/d8-console.h" #include "src/d8/d8-platforms.h" #include "src/d8/d8.h" #include "src/debug/debug-interface.h" #include "src/diagnostics/basic-block-profiler.h" #include "src/execution/vm-state-inl.h" #include "src/init/v8.h" #include "src/interpreter/interpreter.h" #include "src/logging/counters.h" #include "src/objects/managed.h" #include "src/objects/objects-inl.h" #include "src/objects/objects.h" #include "src/parsing/parse-info.h" #include "src/parsing/parsing.h" #include "src/parsing/scanner-character-streams.h" #include "src/sanitizer/msan.h" #include "src/snapshot/natives.h" #include "src/trap-handler/trap-handler.h" #include "src/utils/ostreams.h" #include "src/utils/utils.h" #include "src/wasm/wasm-engine.h" #ifdef V8_USE_PERFETTO #include "perfetto/tracing.h" #endif // V8_USE_PERFETTO #ifdef V8_INTL_SUPPORT #include "unicode/locid.h" #endif // V8_INTL_SUPPORT #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 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_; }; v8::Platform* g_default_platform; std::unique_ptr g_platform; 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; } i::Handle handle = Utils::OpenHandle(*object->GetInternalField(0)); if (handle->IsSmi()) { Throw(isolate, "Worker is defunct because main thread is terminating"); return nullptr; } auto managed = i::Handle>::cast(handle); return managed->raw(); } 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 { static constexpr 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); UpdateIncludedCategoriesList(isolate, context, trace_config_object, trace_config); } private: 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; } }; } // 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() {} ExternalOwningOneByteStringResource( std::unique_ptr file) : file_(std::move(file)) {} const char* data() const override { return static_cast(file_->memory()); } size_t length() const override { return file_->size(); } private: std::unique_ptr file_; }; 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::unordered_set> Shell::running_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_(base::make_unique(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::MutexGuard 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::MutexGuard 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) { if (i::FLAG_parse_only) { i::Isolate* i_isolate = reinterpret_cast(isolate); i::VMState state(i_isolate); i::Handle str = Utils::OpenHandle(*(source)); // Set up ParseInfo. i::ParseInfo parse_info(i_isolate); parse_info.set_toplevel(); parse_info.set_allow_lazy_parsing(); parse_info.set_language_mode( i::construct_language_mode(i::FLAG_use_strict)); parse_info.set_script( parse_info.CreateScript(i_isolate, str, options.compile_options)); if (!i::parsing::ParseProgram(&parse_info, i_isolate)) { fprintf(stderr, "Failed parsing\n"); return false; } return true; } 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