// 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 #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-function.h" #include "include/v8-initialization.h" #include "include/v8-inspector.h" #include "include/v8-json.h" #include "include/v8-locker.h" #include "include/v8-profiler.h" #include "include/v8-wasm.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/platform/wrappers.h" #include "src/base/sanitizer/msan.h" #include "src/base/sys-info.h" #include "src/base/utils/random-number-generator.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/deoptimizer/deoptimizer.h" #include "src/diagnostics/basic-block-profiler.h" #include "src/execution/v8threads.h" #include "src/execution/vm-state-inl.h" #include "src/flags/flags.h" #include "src/handles/maybe-handles.h" #include "src/heap/parked-scope.h" #include "src/init/v8.h" #include "src/interpreter/interpreter.h" #include "src/logging/counters.h" #include "src/logging/log-utils.h" #include "src/objects/managed-inl.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/profiler/profile-generator.h" #include "src/snapshot/snapshot.h" #include "src/tasks/cancelable-task.h" #include "src/trap-handler/trap-handler.h" #include "src/utils/ostreams.h" #include "src/utils/utils.h" #include "src/web-snapshot/web-snapshot.h" #ifdef V8_FUZZILLI #include "src/d8/cov.h" #endif // V8_FUZZILLI #ifdef V8_USE_PERFETTO #include "perfetto/tracing.h" #endif // V8_USE_PERFETTO #ifdef V8_INTL_SUPPORT #include "unicode/locid.h" #endif // V8_INTL_SUPPORT #ifdef V8_OS_LINUX #include // For MultiMappedAllocator. #endif #if !defined(_WIN32) && !defined(_WIN64) #include #else #include #endif // !defined(_WIN32) && !defined(_WIN64) #ifndef DCHECK #define DCHECK(condition) assert(condition) #endif #ifndef CHECK #define CHECK(condition) assert(condition) #endif #define TRACE_BS(...) \ do { \ if (i::FLAG_trace_backing_store) PrintF(__VA_ARGS__); \ } while (false) namespace v8 { namespace { const int kMB = 1024 * 1024; #ifdef V8_FUZZILLI // REPRL = read-eval-print-reset-loop // These file descriptors are being opened when Fuzzilli uses fork & execve to // run V8. #define REPRL_CRFD 100 // Control read file decriptor #define REPRL_CWFD 101 // Control write file decriptor #define REPRL_DRFD 102 // Data read file decriptor #define REPRL_DWFD 103 // Data write file decriptor bool fuzzilli_reprl = true; #else bool fuzzilli_reprl = false; #endif // V8_FUZZILLI 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; void* AllocateVM(size_t length) { DCHECK_LE(kVMThreshold, length); v8::PageAllocator* page_allocator = i::GetArrayBufferPageAllocator(); size_t page_size = page_allocator->AllocatePageSize(); size_t allocated = RoundUp(length, page_size); return i::AllocatePages(page_allocator, nullptr, allocated, page_size, PageAllocator::kReadWrite); } void FreeVM(void* data, size_t length) { v8::PageAllocator* page_allocator = i::GetArrayBufferPageAllocator(); size_t page_size = page_allocator->AllocatePageSize(); size_t allocated = RoundUp(length, page_size); 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_; }; #if MULTI_MAPPED_ALLOCATOR_AVAILABLE // This is a mock allocator variant that provides a huge virtual allocation // backed by a small real allocation that is repeatedly mapped. If you create an // array on memory allocated by this allocator, you will observe that elements // will alias each other as if their indices were modulo-divided by the real // allocation length. // The purpose is to allow stability-testing of huge (typed) arrays without // actually consuming huge amounts of physical memory. // This is currently only available on Linux because it relies on {mremap}. class MultiMappedAllocator : public ArrayBufferAllocatorBase { protected: void* Allocate(size_t length) override { if (length < kChunkSize) { return ArrayBufferAllocatorBase::Allocate(length); } // We use mmap, which initializes pages to zero anyway. return AllocateUninitialized(length); } void* AllocateUninitialized(size_t length) override { if (length < kChunkSize) { return ArrayBufferAllocatorBase::AllocateUninitialized(length); } size_t rounded_length = RoundUp(length, kChunkSize); int prot = PROT_READ | PROT_WRITE; // We have to specify MAP_SHARED to make {mremap} below do what we want. int flags = MAP_SHARED | MAP_ANONYMOUS; void* real_alloc = mmap(nullptr, kChunkSize, prot, flags, -1, 0); if (reinterpret_cast(real_alloc) == -1) { // If we ran into some limit (physical or virtual memory, or number // of mappings, etc), return {nullptr}, which callers can handle. if (errno == ENOMEM) { return nullptr; } // Other errors may be bugs which we want to learn about. FATAL("mmap (real) failed with error %d: %s", errno, strerror(errno)); } void* virtual_alloc = mmap(nullptr, rounded_length, prot, flags | MAP_NORESERVE, -1, 0); if (reinterpret_cast(virtual_alloc) == -1) { if (errno == ENOMEM) { // Undo earlier, successful mappings. munmap(real_alloc, kChunkSize); return nullptr; } FATAL("mmap (virtual) failed with error %d: %s", errno, strerror(errno)); } i::Address virtual_base = reinterpret_cast(virtual_alloc); i::Address virtual_end = virtual_base + rounded_length; for (i::Address to_map = virtual_base; to_map < virtual_end; to_map += kChunkSize) { // Specifying 0 as the "old size" causes the existing map entry to not // get deleted, which is important so that we can remap it again in the // next iteration of this loop. void* result = mremap(real_alloc, 0, kChunkSize, MREMAP_MAYMOVE | MREMAP_FIXED, reinterpret_cast(to_map)); if (reinterpret_cast(result) == -1) { if (errno == ENOMEM) { // Undo earlier, successful mappings. munmap(real_alloc, kChunkSize); munmap(virtual_alloc, (to_map - virtual_base)); return nullptr; } FATAL("mremap failed with error %d: %s", errno, strerror(errno)); } } base::MutexGuard lock_guard(®ions_mutex_); regions_[virtual_alloc] = real_alloc; return virtual_alloc; } void Free(void* data, size_t length) override { if (length < kChunkSize) { return ArrayBufferAllocatorBase::Free(data, length); } base::MutexGuard lock_guard(®ions_mutex_); void* real_alloc = regions_[data]; munmap(real_alloc, kChunkSize); size_t rounded_length = RoundUp(length, kChunkSize); munmap(data, rounded_length); regions_.erase(data); } private: // Aiming for a "Huge Page" (2M on Linux x64) to go easy on the TLB. static constexpr size_t kChunkSize = 2 * 1024 * 1024; std::unordered_map regions_; base::Mutex regions_mutex_; }; #endif // MULTI_MAPPED_ALLOCATOR_AVAILABLE v8::Platform* g_default_platform; std::unique_ptr g_platform; static MaybeLocal TryGetValue(v8::Isolate* isolate, Local context, Local object, const char* property) { MaybeLocal v8_str = String::NewFromUtf8(isolate, property); if (v8_str.IsEmpty()) return {}; return object->Get(context, v8_str.ToLocalChecked()); } static Local GetValue(v8::Isolate* isolate, Local context, Local object, const char* property) { return TryGetValue(isolate, context, object, property).ToLocalChecked(); } std::shared_ptr GetWorkerFromInternalField(Isolate* isolate, Local object) { if (object->InternalFieldCount() != 1) { isolate->ThrowError("this is not a Worker"); return nullptr; } i::Handle handle = Utils::OpenHandle(*object->GetInternalField(0)); if (handle->IsSmi()) { isolate->ThrowError("Worker is defunct because main thread is terminating"); return nullptr; } auto managed = i::Handle>::cast(handle); return managed->get(); } 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).ToLocalChecked(); Local result = JSON::Parse(context, source).ToLocalChecked(); Local trace_config_object = result.As(); 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 = value.As(); 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() = default; 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_; }; // static variables: CounterMap* Shell::counter_map_; base::SharedMutex Shell::counter_mutex_; 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::Now(); Global Shell::stringify_function_; base::LazyMutex Shell::workers_mutex_; bool Shell::allow_new_workers_ = true; std::unordered_set> Shell::running_workers_; std::atomic Shell::script_executed_{false}; std::atomic Shell::valid_fuzz_script_{false}; base::LazyMutex Shell::isolate_status_lock_; std::map Shell::isolate_status_; std::map Shell::isolate_running_streaming_tasks_; base::LazyMutex Shell::cached_code_mutex_; std::map> Shell::cached_code_map_; std::atomic Shell::unhandled_promise_rejections_{0}; Global Shell::evaluation_context_; ArrayBuffer::Allocator* Shell::array_buffer_allocator; Isolate* Shell::shared_isolate = nullptr; bool check_d8_flag_contradictions = true; ShellOptions Shell::options; base::OnceType Shell::quit_once_ = V8_ONCE_INIT; 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)); } // Dummy external source stream which returns the whole source in one go. // TODO(leszeks): Also test chunking the data. class DummySourceStream : public v8::ScriptCompiler::ExternalSourceStream { public: explicit DummySourceStream(Local source) : done_(false) { source_buffer_ = Utils::OpenHandle(*source)->ToCString( i::ALLOW_NULLS, i::FAST_STRING_TRAVERSAL, &source_length_); } size_t GetMoreData(const uint8_t** src) override { if (done_) { return 0; } *src = reinterpret_cast(source_buffer_.release()); done_ = true; return source_length_; } private: int source_length_; std::unique_ptr source_buffer_; bool done_; }; class StreamingCompileTask final : public v8::Task { public: StreamingCompileTask(Isolate* isolate, v8::ScriptCompiler::StreamedSource* streamed_source, v8::ScriptType type) : isolate_(isolate), script_streaming_task_(v8::ScriptCompiler::StartStreaming( isolate, streamed_source, type)) { Shell::NotifyStartStreamingTask(isolate_); } void Run() override { script_streaming_task_->Run(); // Signal that the task has finished using the task runner to wake the // message loop. Shell::PostForegroundTask(isolate_, std::make_unique(isolate_)); } private: class FinishTask final : public v8::Task { public: explicit FinishTask(Isolate* isolate) : isolate_(isolate) {} void Run() final { Shell::NotifyFinishStreamingTask(isolate_); } Isolate* isolate_; }; Isolate* isolate_; std::unique_ptr script_streaming_task_; }; namespace { template MaybeLocal CompileStreamed(Local context, ScriptCompiler::StreamedSource* v8_source, Local full_source_string, const ScriptOrigin& origin) {} template <> MaybeLocal