| // 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 <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| #include <algorithm> |
| #include <fstream> |
| #include <iomanip> |
| #include <iterator> |
| #include <string> |
| #include <tuple> |
| #include <type_traits> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #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-isolate.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/memory.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/compiler-dispatcher/optimizing-compile-dispatcher.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/microtask-queue.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-file.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/sandbox/testing.h" |
| #include "src/snapshot/snapshot.h" |
| #include "src/tasks/cancelable-task.h" |
| #include "src/utils/ostreams.h" |
| #include "src/utils/utils.h" |
| |
| #ifdef V8_ENABLE_MAGLEV |
| #include "src/maglev/maglev-concurrent-dispatcher.h" |
| #endif // V8_ENABLE_MAGLEV |
| |
| #if V8_OS_POSIX |
| #include <signal.h> |
| #endif // V8_OS_POSIX |
| |
| #ifdef V8_FUZZILLI |
| #include "src/d8/cov.h" |
| #endif // V8_FUZZILLI |
| |
| #ifdef V8_USE_PERFETTO |
| #include "perfetto/tracing/track_event.h" |
| #include "perfetto/tracing/track_event_legacy.h" |
| #endif // V8_USE_PERFETTO |
| |
| #ifdef V8_INTL_SUPPORT |
| #include "unicode/locid.h" |
| #endif // V8_INTL_SUPPORT |
| |
| #ifdef V8_OS_LINUX |
| #include <sys/mman.h> // For MultiMappedAllocator. |
| #endif |
| |
| #if !defined(_WIN32) && !defined(_WIN64) |
| #include <unistd.h> |
| #else |
| #include <windows.h> |
| #endif // !defined(_WIN32) && !defined(_WIN64) |
| |
| #if V8_ENABLE_WEBASSEMBLY |
| #include "src/trap-handler/trap-handler.h" |
| #endif // V8_ENABLE_WEBASSEMBLY |
| |
| #ifndef DCHECK |
| #define DCHECK(condition) assert(condition) |
| #endif |
| |
| #ifndef CHECK |
| #define CHECK(condition) assert(condition) |
| #endif |
| |
| namespace v8 { |
| |
| namespace { |
| |
| #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 |
| |
| // 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> allocator_ = |
| std::unique_ptr<Allocator>(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 * i::MB; |
| 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<size_t> 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<intptr_t>(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<intptr_t>(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<i::Address>(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<void*>(to_map)); |
| if (reinterpret_cast<intptr_t>(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<void*, void*> regions_; |
| base::Mutex regions_mutex_; |
| }; |
| |
| #endif // MULTI_MAPPED_ALLOCATOR_AVAILABLE |
| |
| v8::Platform* g_default_platform; |
| std::unique_ptr<v8::Platform> g_platform; |
| |
| template <int N> |
| bool ThrowError(Isolate* isolate, const char (&message)[N]) { |
| if (isolate->IsExecutionTerminating()) return false; |
| isolate->ThrowError(message); |
| return true; |
| } |
| |
| bool ThrowError(Isolate* isolate, Local<String> message) { |
| if (isolate->IsExecutionTerminating()) return false; |
| isolate->ThrowError(message); |
| return true; |
| } |
| |
| static MaybeLocal<Value> TryGetValue(v8::Isolate* isolate, |
| Local<Context> context, |
| Local<v8::Object> object, |
| const char* property) { |
| MaybeLocal<String> v8_str = String::NewFromUtf8(isolate, property); |
| if (v8_str.IsEmpty()) return {}; |
| return object->Get(context, v8_str.ToLocalChecked()); |
| } |
| |
| static Local<Value> GetValue(v8::Isolate* isolate, Local<Context> context, |
| Local<v8::Object> object, const char* property) { |
| return TryGetValue(isolate, context, object, property).ToLocalChecked(); |
| } |
| |
| std::shared_ptr<Worker> GetWorkerFromInternalField(Isolate* isolate, |
| Local<Object> object) { |
| if (object->InternalFieldCount() != 1) { |
| ThrowError(isolate, "this is not a Worker"); |
| return nullptr; |
| } |
| |
| i::Handle<i::Object> handle = Utils::OpenHandle(*object->GetInternalField(0)); |
| if (handle->IsSmi()) { |
| ThrowError(isolate, "Worker is defunct because main thread is terminating"); |
| return nullptr; |
| } |
| auto managed = i::Handle<i::Managed<Worker>>::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 * i::MB); |
| } |
| |
| } // namespace |
| |
| namespace tracing { |
| |
| namespace { |
| |
| static constexpr char kIncludedCategoriesParam[] = "included_categories"; |
| static constexpr char kTraceConfigParam[] = "trace_config"; |
| |
| 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 = Context::New(isolate); |
| Context::Scope context_scope(context); |
| HandleScope inner_scope(isolate); |
| |
| Local<String> source = |
| String::NewFromUtf8(isolate, json_str).ToLocalChecked(); |
| Local<Value> result = JSON::Parse(context, source).ToLocalChecked(); |
| Local<v8::Object> trace_config_object = result.As<v8::Object>(); |
| // Try reading 'trace_config' property from a full chrome trace config. |
| // https://chromium.googlesource.com/chromium/src/+/master/docs/memory-infra/memory_infra_startup_tracing.md#the-advanced-way |
| Local<Value> maybe_trace_config_object = |
| GetValue(isolate, context, trace_config_object, kTraceConfigParam); |
| if (maybe_trace_config_object->IsObject()) { |
| trace_config_object = maybe_trace_config_object.As<Object>(); |
| } |
| |
| UpdateIncludedCategoriesList(isolate, context, trace_config_object, |
| trace_config); |
| } |
| |
| private: |
| static int UpdateIncludedCategoriesList( |
| v8::Isolate* isolate, Local<Context> context, Local<v8::Object> object, |
| platform::tracing::TraceConfig* trace_config) { |
| Local<Value> value = |
| GetValue(isolate, context, object, kIncludedCategoriesParam); |
| if (value->IsArray()) { |
| Local<Array> v8_array = value.As<Array>(); |
| for (int i = 0, length = v8_array->Length(); i < length; ++i) { |
| Local<Value> 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<base::OS::MemoryMappedFile> file) |
| : file_(std::move(file)) {} |
| const char* data() const override { |
| return static_cast<char*>(file_->memory()); |
| } |
| size_t length() const override { return file_->size(); } |
| |
| private: |
| std::unique_ptr<base::OS::MemoryMappedFile> 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<Function> Shell::stringify_function_; |
| base::Mutex Shell::profiler_end_callback_lock_; |
| std::map<Isolate*, std::pair<Global<Function>, Global<Context>>> |
| Shell::profiler_end_callback_; |
| base::LazyMutex Shell::workers_mutex_; |
| bool Shell::allow_new_workers_ = true; |
| std::unordered_set<std::shared_ptr<Worker>> Shell::running_workers_; |
| std::atomic<bool> Shell::script_executed_{false}; |
| std::atomic<bool> Shell::valid_fuzz_script_{false}; |
| base::LazyMutex Shell::isolate_status_lock_; |
| std::map<v8::Isolate*, bool> Shell::isolate_status_; |
| std::map<v8::Isolate*, int> Shell::isolate_running_streaming_tasks_; |
| base::LazyMutex Shell::cached_code_mutex_; |
| std::map<std::string, std::unique_ptr<ScriptCompiler::CachedData>> |
| Shell::cached_code_map_; |
| std::atomic<int> Shell::unhandled_promise_rejections_{0}; |
| |
| Global<Context> Shell::evaluation_context_; |
| ArrayBuffer::Allocator* Shell::array_buffer_allocator; |
| bool check_d8_flag_contradictions = true; |
| ShellOptions Shell::options; |
| base::OnceType Shell::quit_once_ = V8_ONCE_INIT; |
| |
| ScriptCompiler::CachedData* Shell::LookupCodeCache(Isolate* isolate, |
| Local<Value> source) { |
| i::ParkedMutexGuard lock_guard( |
| reinterpret_cast<i::Isolate*>(isolate)->main_thread_local_isolate(), |
| 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<Value> source, |
| const ScriptCompiler::CachedData* cache_data) { |
| i::ParkedMutexGuard lock_guard( |
| reinterpret_cast<i::Isolate*>(isolate)->main_thread_local_isolate(), |
| 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<ScriptCompiler::CachedData>( |
| 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<String> 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<uint8_t*>(source_buffer_.release()); |
| done_ = true; |
| |
| return source_length_; |
| } |
| |
| private: |
| int source_length_; |
| std::unique_ptr<char[]> 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<FinishTask>(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<v8::ScriptCompiler::ScriptStreamingTask> |
| script_streaming_task_; |
| }; |
| |
| namespace { |
| template <class T> |
| MaybeLocal<T> CompileStreamed(Local<Context> context, |
| ScriptCompiler::StreamedSource* v8_source, |
| Local<String> full_source_string, |
| const ScriptOrigin& origin) {} |
| |
| template <> |
| MaybeLocal<Script> CompileStreamed(Local<Context> context, |
| ScriptCompiler::StreamedSource* v8_source, |
| Local<String> full_source_string, |
| const ScriptOrigin& origin) { |
| return ScriptCompiler::Compile(context, v8_source, full_source_string, |
| origin); |
| } |
| |
| template <> |
| MaybeLocal<Module> CompileStreamed(Local<Context> context, |
| ScriptCompiler::StreamedSource* v8_source, |
| Local<String> full_source_string, |
| const ScriptOrigin& origin) { |
| return ScriptCompiler::CompileModule(context, v8_source, full_source_string, |
| origin); |
| } |
| |
| template <class T> |
| MaybeLocal<T> Compile(Local<Context> context, ScriptCompiler::Source* source, |
| ScriptCompiler::CompileOptions options) {} |
| template <> |
| MaybeLocal<Script> Compile(Local<Context> context, |
| ScriptCompiler::Source* source, |
| ScriptCompiler::CompileOptions options) { |
| return ScriptCompiler::Compile(context, source, options); |
| } |
| |
| template <> |
| MaybeLocal<Module> Compile(Local<Context> context, |
| ScriptCompiler::Source* source, |
| ScriptCompiler::CompileOptions options) { |
| return ScriptCompiler::CompileModule(context->GetIsolate(), source, options); |
| } |
| |
| } // namespace |
| |
| template <class T> |
| MaybeLocal<T> Shell::CompileString(Isolate* isolate, Local<Context> context, |
| Local<String> source, |
| const ScriptOrigin& origin) { |
| if (options.streaming_compile) { |
| v8::ScriptCompiler::StreamedSource streamed_source( |
| std::make_unique<DummySourceStream>(source), |
| v8::ScriptCompiler::StreamedSource::UTF8); |
| PostBlockingBackgroundTask(std::make_unique<StreamingCompileTask>( |
| isolate, &streamed_source, |
| std::is_same<T, Module>::value ? v8::ScriptType::kModule |
| : v8::ScriptType::kClassic)); |
| // Pump the loop until the streaming task completes. |
| Shell::CompleteMessageLoop(isolate); |
| return CompileStreamed<T>(context, &streamed_source, source, origin); |
| } |
| |
| ScriptCompiler::CachedData* cached_code = nullptr; |
| if (options.compile_options == ScriptCompiler::kConsumeCodeCache) { |
| cached_code = LookupCodeCache(isolate, source); |
| } |
| ScriptCompiler::Source script_source(source, origin, cached_code); |
| MaybeLocal<T> result = |
| Compile<T>(context, &script_source, |
| cached_code ? ScriptCompiler::kConsumeCodeCache |
| : ScriptCompiler::kNoCompileOptions); |
| if (cached_code) CHECK(!cached_code->rejected); |
| return result; |
| } |
| |
| namespace { |
| // For testing. |
| const int kHostDefinedOptionsLength = 2; |
| const uint32_t kHostDefinedOptionsMagicConstant = 0xF1F2F3F0; |
| |
| std::string ToSTLString(Isolate* isolate, Local<String> v8_str) { |
| String::Utf8Value utf8(isolate, v8_str); |
| // Should not be able to fail since the input is a String. |
| CHECK(*utf8); |
| return *utf8; |
| } |
| |
| // Per-context Module data, allowing sharing of module maps |
| // across top-level module loads. |
| class ModuleEmbedderData { |
| private: |
| class ModuleGlobalHash { |
| public: |
| explicit ModuleGlobalHash(Isolate* isolate) : isolate_(isolate) {} |
| size_t operator()(const Global<Module>& module) const { |
| return module.Get(isolate_)->GetIdentityHash(); |
| } |
| |
| private: |
| Isolate* isolate_; |
| }; |
| |
| public: |
| explicit ModuleEmbedderData(Isolate* isolate) |
| : module_to_specifier_map(10, ModuleGlobalHash(isolate)), |
| json_module_to_parsed_json_map( |
| 10, module_to_specifier_map.hash_function()) {} |
| |
| static ModuleType ModuleTypeFromImportAssertions( |
| Local<Context> context, Local<FixedArray> import_assertions, |
| bool hasPositions) { |
| Isolate* isolate = context->GetIsolate(); |
| const int kV8AssertionEntrySize = hasPositions ? 3 : 2; |
| for (int i = 0; i < import_assertions->Length(); |
| i += kV8AssertionEntrySize) { |
| Local<String> v8_assertion_key = |
| import_assertions->Get(context, i).As<v8::String>(); |
| std::string assertion_key = ToSTLString(isolate, v8_assertion_key); |
| |
| if (assertion_key == "type") { |
| Local<String> v8_assertion_value = |
| import_assertions->Get(context, i + 1).As<String>(); |
| std::string assertion_value = ToSTLString(isolate, v8_assertion_value); |
| if (assertion_value == "json") { |
| return ModuleType::kJSON; |
| } else { |
| // JSON is currently the only supported non-JS type |
| return ModuleType::kInvalid; |
| } |
| } |
| } |
| |
| // If no type is asserted, default to JS. |
| return ModuleType::kJavaScript; |
| } |
| |
| // Map from (normalized module specifier, module type) pair to Module. |
| std::map<std::pair<std::string, ModuleType>, Global<Module>> module_map; |
| // Map from Module to its URL as defined in the ScriptOrigin |
| std::unordered_map<Global<Module>, std::string, ModuleGlobalHash> |
| module_to_specifier_map; |
| // Map from JSON Module to its parsed content, for use in module |
| // JSONModuleEvaluationSteps |
| std::unordered_map<Global<Module>, Global<Value>, ModuleGlobalHash> |
| json_module_to_parsed_json_map; |
| |
| // Origin location used for resolving modules when referrer is null. |
| std::string origin; |
| }; |
| |
| enum { kModuleEmbedderDataIndex, kInspectorClientIndex }; |
| |
| std::shared_ptr<ModuleEmbedderData> InitializeModuleEmbedderData( |
| Local<Context> context) { |
| i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate()); |
| const size_t kModuleEmbedderDataEstimate = 4 * 1024; // module map. |
| i::Handle<i::Managed<ModuleEmbedderData>> module_data_managed = |
| i::Managed<ModuleEmbedderData>::Allocate( |
| i_isolate, kModuleEmbedderDataEstimate, context->GetIsolate()); |
| v8::Local<v8::Value> module_data = Utils::ToLocal(module_data_managed); |
| context->SetEmbedderData(kModuleEmbedderDataIndex, module_data); |
| return module_data_managed->get(); |
| } |
| |
| std::shared_ptr<ModuleEmbedderData> GetModuleDataFromContext( |
| Local<Context> context) { |
| v8::Local<v8::Value> module_data = |
| context->GetEmbedderData(kModuleEmbedderDataIndex); |
| i::Handle<i::Managed<ModuleEmbedderData>> module_data_managed = |
| i::Handle<i::Managed<ModuleEmbedderData>>::cast( |
| Utils::OpenHandle<Value, i::Object>(module_data)); |
| return module_data_managed->get(); |
| } |
| |
| ScriptOrigin CreateScriptOrigin(Isolate* isolate, Local<String> resource_name, |
| v8::ScriptType type) { |
| Local<PrimitiveArray> options = |
| PrimitiveArray::New(isolate, kHostDefinedOptionsLength); |
| options->Set(isolate, 0, |
| v8::Uint32::New(isolate, kHostDefinedOptionsMagicConstant)); |
| options->Set(isolate, 1, resource_name); |
| return ScriptOrigin(isolate, resource_name, 0, 0, false, -1, Local<Value>(), |
| false, false, type == v8::ScriptType::kModule, options); |
| } |
| |
| bool IsValidHostDefinedOptions(Local<Context> context, Local<Data> options, |
| Local<Value> resource_name) { |
| if (!options->IsFixedArray()) return false; |
| Local<FixedArray> array = options.As<FixedArray>(); |
| if (array->Length() != kHostDefinedOptionsLength) return false; |
| uint32_t magic = 0; |
| if (!array->Get(context, 0).As<Value>()->Uint32Value(context).To(&magic)) { |
| return false; |
| } |
| if (magic != kHostDefinedOptionsMagicConstant) return false; |
| return array->Get(context, 1).As<String>()->StrictEquals(resource_name); |
| } |
| |
| class D8WasmAsyncResolvePromiseTask : public v8::Task { |
| public: |
| D8WasmAsyncResolvePromiseTask(v8::Isolate* isolate, |
| v8::Local<v8::Context> context, |
| v8::Local<v8::Promise::Resolver> resolver, |
| v8::Local<v8::Value> result, |
| WasmAsyncSuccess success) |
| : isolate_(isolate), |
| context_(isolate, context), |
| resolver_(isolate, resolver), |
| result_(isolate, result), |
| success_(success) {} |
| |
| void Run() override { |
| v8::HandleScope scope(isolate_); |
| v8::Local<v8::Context> context = context_.Get(isolate_); |
| MicrotasksScope microtasks_scope(context, |
| MicrotasksScope::kDoNotRunMicrotasks); |
| v8::Local<v8::Promise::Resolver> resolver = resolver_.Get(isolate_); |
| v8::Local<v8::Value> result = result_.Get(isolate_); |
| |
| Maybe<bool> ret = success_ == WasmAsyncSuccess::kSuccess |
| ? resolver->Resolve(context, result) |
| : resolver->Reject(context, result); |
| // It's guaranteed that no exceptions will be thrown by these |
| // operations, but execution might be terminating. |
| CHECK(ret.IsJust() ? ret.FromJust() : isolate_->IsExecutionTerminating()); |
| } |
| |
| private: |
| v8::Isolate* isolate_; |
| v8::Global<v8::Context> context_; |
| v8::Global<v8::Promise::Resolver> resolver_; |
| v8::Global<v8::Value> result_; |
| WasmAsyncSuccess success_; |
| }; |
| |
| void D8WasmAsyncResolvePromiseCallback( |
| v8::Isolate* isolate, v8::Local<v8::Context> context, |
| v8::Local<v8::Promise::Resolver> resolver, v8::Local<v8::Value> result, |
| WasmAsyncSuccess success) { |
| // We have to resolve the promise in a separate task which is not a cancelable |
| // task, to avoid a deadlock when {quit()} is called in the then-handler of |
| // the result promise. |
| g_platform->GetForegroundTaskRunner(isolate)->PostTask( |
| std::make_unique<D8WasmAsyncResolvePromiseTask>( |
| isolate, context, resolver, result, success)); |
| } |
| |
| } // namespace |
| |
| // Executes a string within the current v8 context. |
| bool Shell::ExecuteString(Isolate* isolate, Local<String> source, |
| Local<String> name, PrintResult print_result, |
| ReportExceptions report_exceptions, |
| ProcessMessageQueue process_message_queue) { |
| i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| if (i::v8_flags.parse_only) { |
| i::VMState<PARSER> state(i_isolate); |
| i::Handle<i::String> str = Utils::OpenHandle(*(source)); |
| |
| // Set up ParseInfo. |
| i::UnoptimizedCompileState compile_state; |
| i::ReusableUnoptimizedCompileState reusable_state(i_isolate); |
| |
| i::UnoptimizedCompileFlags flags = |
| i::UnoptimizedCompileFlags::ForToplevelCompile( |
| i_isolate, true, i::construct_language_mode(i::v8_flags.use_strict), |
| i::REPLMode::kNo, ScriptType::kClassic, i::v8_flags.lazy); |
| |
| if (options.compile_options == v8::ScriptCompiler::kEagerCompile) { |
| flags.set_is_eager(true); |
| } |
| |
| i::ParseInfo parse_info(i_isolate, flags, &compile_state, &reusable_state); |
| |
| i::Handle<i::Script> script = parse_info.CreateScript( |
| i_isolate, str, i::kNullMaybeHandle, ScriptOriginOptions()); |
| if (!i::parsing::ParseProgram(&parse_info, script, i_isolate, |
| i::parsing::ReportStatisticsMode::kYes)) { |
| parse_info.pending_error_handler()->PrepareErrors( |
| i_isolate, parse_info.ast_value_factory()); |
| parse_info.pending_error_handler()->ReportErrors(i_isolate, script); |
| |
| fprintf(stderr, "Failed parsing\n"); |
| return false; |
| } |
| return true; |
| } |
| |
| HandleScope handle_scope(isolate); |
| TryCatch try_catch(isolate); |
| try_catch.SetVerbose(report_exceptions == kReportExceptions); |
| |
| // Explicitly check for stack overflows. This method can be called |
| // recursively, and since we consume quite some stack space for the C++ |
| // frames, the stack check in the called frame might be too late. |
| if (i::StackLimitCheck{i_isolate}.HasOverflowed()) { |
| i_isolate->StackOverflow(); |
| i_isolate->OptionalRescheduleException(false); |
| return false; |
| } |
| |
| MaybeLocal<Value> maybe_result; |
| bool success = true; |
| { |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| Local<Context> realm = |
| Local<Context>::New(isolate, data->realms_[data->realm_current_]); |
| Context::Scope context_scope(realm); |
| Local<Context> context(isolate->GetCurrentContext()); |
| ScriptOrigin origin = |
| CreateScriptOrigin(isolate, name, ScriptType::kClassic); |
| |
| std::shared_ptr<ModuleEmbedderData> module_data = |
| GetModuleDataFromContext(realm); |
| module_data->origin = ToSTLString(isolate, name); |
| |
| for (int i = 1; i < options.repeat_compile; ++i) { |
| HandleScope handle_scope_for_compiling(isolate); |
| if (CompileString<Script>(isolate, context, source, origin).IsEmpty()) { |
| return false; |
| } |
| } |
| Local<Script> script; |
| if (!CompileString<Script>(isolate, context, source, origin) |
| .ToLocal(&script)) { |
| return false; |
| } |
| |
| if (options.code_cache_options == |
| ShellOptions::CodeCacheOptions::kProduceCache) { |
| // Serialize and store it in memory for the next execution. |
| ScriptCompiler::CachedData* cached_data = |
| ScriptCompiler::CreateCodeCache(script->GetUnboundScript()); |
| StoreInCodeCache(isolate, source, cached_data); |
| delete cached_data; |
| } |
| if (options.compile_only) return true; |
| if (options.compile_options == ScriptCompiler::kConsumeCodeCache) { |
| i::Handle<i::Script> i_script( |
| i::Script::cast(Utils::OpenHandle(*script)->shared().script()), |
| i_isolate); |
| // TODO(cbruni, chromium:1244145): remove once context-allocated. |
| i_script->set_host_defined_options(i::FixedArray::cast( |
| *Utils::OpenHandle(*(origin.GetHostDefinedOptions())))); |
| } |
| maybe_result = script->Run(realm); |
| if (options.code_cache_options == |
| ShellOptions::CodeCacheOptions::kProduceCacheAfterExecute) { |
| // Serialize and store it in memory for the next execution. |
| ScriptCompiler::CachedData* cached_data = |
| ScriptCompiler::CreateCodeCache(script->GetUnboundScript()); |
| StoreInCodeCache(isolate, source, cached_data); |
| delete cached_data; |
| } |
| if (process_message_queue) { |
| if (!EmptyMessageQueues(isolate)) success = false; |
| if (!HandleUnhandledPromiseRejections(isolate)) success = false; |
| } |
| data->realm_current_ = data->realm_switch_; |
| } |
| Local<Value> result; |
| if (!maybe_result.ToLocal(&result)) { |
| DCHECK(try_catch.HasCaught()); |
| return false; |
| } |
| // It's possible that a FinalizationRegistry cleanup task threw an error. |
| if (try_catch.HasCaught()) success = false; |
| if (print_result) { |
| if (options.test_shell) { |
| if (!result->IsUndefined()) { |
| // If all went well and the result wasn't undefined then print |
| // the returned value. |
| v8::String::Utf8Value str(isolate, result); |
| fwrite(*str, sizeof(**str), str.length(), stdout); |
| printf("\n"); |
| } |
| } else { |
| v8::String::Utf8Value str(isolate, Stringify(isolate, result)); |
| fwrite(*str, sizeof(**str), str.length(), stdout); |
| printf("\n"); |
| } |
| } |
| return success; |
| } |
| |
| namespace { |
| |
| bool IsAbsolutePath(const std::string& path) { |
| #if defined(_WIN32) || defined(_WIN64) |
| // This is an incorrect approximation, but should |
| // work for all our test-running cases. |
| return path.find(':') != std::string::npos; |
| #else |
| return path[0] == '/'; |
| #endif |
| } |
| |
| std::string GetWorkingDirectory() { |
| #if defined(_WIN32) || defined(_WIN64) |
| char system_buffer[MAX_PATH]; |
| // Unicode paths are unsupported, which is fine as long as |
| // the test directory doesn't include any such paths. |
| DWORD len = GetCurrentDirectoryA(MAX_PATH, system_buffer); |
| CHECK_GT(len, 0); |
| return system_buffer; |
| #else |
| char curdir[PATH_MAX]; |
| CHECK_NOT_NULL(getcwd(curdir, PATH_MAX)); |
| return curdir; |
| #endif |
| } |
| |
| // Returns the directory part of path, without the trailing '/'. |
| std::string DirName(const std::string& path) { |
| DCHECK(IsAbsolutePath(path)); |
| size_t last_slash = path.find_last_of('/'); |
| DCHECK(last_slash != std::string::npos); |
| return path.substr(0, last_slash); |
| } |
| |
| // Resolves path to an absolute path if necessary, and does some |
| // normalization (eliding references to the current directory |
| // and replacing backslashes with slashes). |
| std::string NormalizePath(const std::string& path, |
| const std::string& dir_name) { |
| std::string absolute_path; |
| if (IsAbsolutePath(path)) { |
| absolute_path = path; |
| } else { |
| absolute_path = dir_name + '/' + path; |
| } |
| std::replace(absolute_path.begin(), absolute_path.end(), '\\', '/'); |
| std::vector<std::string> segments; |
| std::istringstream segment_stream(absolute_path); |
| std::string segment; |
| while (std::getline(segment_stream, segment, '/')) { |
| if (segment == "..") { |
| if (!segments.empty()) segments.pop_back(); |
| } else if (segment != ".") { |
| segments.push_back(segment); |
| } |
| } |
| // Join path segments. |
| std::ostringstream os; |
| if (segments.size() > 1) { |
| std::copy(segments.begin(), segments.end() - 1, |
| std::ostream_iterator<std::string>(os, "/")); |
| os << *segments.rbegin(); |
| } else { |
| os << "/"; |
| if (!segments.empty()) os << segments[0]; |
| } |
| return os.str(); |
| } |
| |
| MaybeLocal<Module> ResolveModuleCallback(Local<Context> context, |
| Local<String> specifier, |
| Local<FixedArray> import_assertions, |
| Local<Module> referrer) { |
| Isolate* isolate = context->GetIsolate(); |
| std::shared_ptr<ModuleEmbedderData> module_data = |
| GetModuleDataFromContext(context); |
| auto specifier_it = module_data->module_to_specifier_map.find( |
| Global<Module>(isolate, referrer)); |
| CHECK(specifier_it != module_data->module_to_specifier_map.end()); |
| std::string absolute_path = NormalizePath(ToSTLString(isolate, specifier), |
| DirName(specifier_it->second)); |
| ModuleType module_type = ModuleEmbedderData::ModuleTypeFromImportAssertions( |
| context, import_assertions, true); |
| auto module_it = |
| module_data->module_map.find(std::make_pair(absolute_path, module_type)); |
| CHECK(module_it != module_data->module_map.end()); |
| return module_it->second.Get(isolate); |
| } |
| |
| } // anonymous namespace |
| |
| MaybeLocal<Module> Shell::FetchModuleTree(Local<Module> referrer, |
| Local<Context> context, |
| const std::string& file_name, |
| ModuleType module_type) { |
| DCHECK(IsAbsolutePath(file_name)); |
| Isolate* isolate = context->GetIsolate(); |
| MaybeLocal<String> source_text = ReadFile(isolate, file_name.c_str(), false); |
| if (source_text.IsEmpty() && options.fuzzy_module_file_extensions) { |
| std::string fallback_file_name = file_name + ".js"; |
| source_text = ReadFile(isolate, fallback_file_name.c_str(), false); |
| if (source_text.IsEmpty()) { |
| fallback_file_name = file_name + ".mjs"; |
| source_text = ReadFile(isolate, fallback_file_name.c_str()); |
| } |
| } |
| |
| std::shared_ptr<ModuleEmbedderData> module_data = |
| GetModuleDataFromContext(context); |
| if (source_text.IsEmpty()) { |
| std::string msg = "d8: Error reading module from " + file_name; |
| if (!referrer.IsEmpty()) { |
| auto specifier_it = module_data->module_to_specifier_map.find( |
| Global<Module>(isolate, referrer)); |
| CHECK(specifier_it != module_data->module_to_specifier_map.end()); |
| msg += "\n imported by " + specifier_it->second; |
| } |
| ThrowError(isolate, |
| v8::String::NewFromUtf8(isolate, msg.c_str()).ToLocalChecked()); |
| return MaybeLocal<Module>(); |
| } |
| |
| Local<String> resource_name = |
| String::NewFromUtf8(isolate, file_name.c_str()).ToLocalChecked(); |
| ScriptOrigin origin = |
| CreateScriptOrigin(isolate, resource_name, ScriptType::kModule); |
| |
| Local<Module> module; |
| if (module_type == ModuleType::kJavaScript) { |
| ScriptCompiler::Source source(source_text.ToLocalChecked(), origin); |
| if (!CompileString<Module>(isolate, context, source_text.ToLocalChecked(), |
| origin) |
| .ToLocal(&module)) { |
| return MaybeLocal<Module>(); |
| } |
| } else if (module_type == ModuleType::kJSON) { |
| Local<Value> parsed_json; |
| if (!v8::JSON::Parse(context, source_text.ToLocalChecked()) |
| .ToLocal(&parsed_json)) { |
| return MaybeLocal<Module>(); |
| } |
| |
| std::vector<Local<String>> export_names{ |
| String::NewFromUtf8(isolate, "default").ToLocalChecked()}; |
| |
| module = v8::Module::CreateSyntheticModule( |
| isolate, |
| String::NewFromUtf8(isolate, file_name.c_str()).ToLocalChecked(), |
| export_names, Shell::JSONModuleEvaluationSteps); |
| |
| CHECK(module_data->json_module_to_parsed_json_map |
| .insert(std::make_pair(Global<Module>(isolate, module), |
| Global<Value>(isolate, parsed_json))) |
| .second); |
| } else { |
| UNREACHABLE(); |
| } |
| |
| CHECK(module_data->module_map |
| .insert(std::make_pair(std::make_pair(file_name, module_type), |
| Global<Module>(isolate, module))) |
| .second); |
| CHECK(module_data->module_to_specifier_map |
| .insert(std::make_pair(Global<Module>(isolate, module), file_name)) |
| .second); |
| |
| std::string dir_name = DirName(file_name); |
| |
| Local<FixedArray> module_requests = module->GetModuleRequests(); |
| for (int i = 0, length = module_requests->Length(); i < length; ++i) { |
| Local<ModuleRequest> module_request = |
| module_requests->Get(context, i).As<ModuleRequest>(); |
| Local<String> name = module_request->GetSpecifier(); |
| std::string absolute_path = |
| NormalizePath(ToSTLString(isolate, name), dir_name); |
| Local<FixedArray> import_assertions = module_request->GetImportAssertions(); |
| ModuleType request_module_type = |
| ModuleEmbedderData::ModuleTypeFromImportAssertions( |
| context, import_assertions, true); |
| |
| if (request_module_type == ModuleType::kInvalid) { |
| ThrowError(isolate, "Invalid module type was asserted"); |
| return MaybeLocal<Module>(); |
| } |
| |
| if (module_data->module_map.count( |
| std::make_pair(absolute_path, request_module_type))) { |
| continue; |
| } |
| |
| if (FetchModuleTree(module, context, absolute_path, request_module_type) |
| .IsEmpty()) { |
| return MaybeLocal<Module>(); |
| } |
| } |
| |
| return module; |
| } |
| |
| MaybeLocal<Value> Shell::JSONModuleEvaluationSteps(Local<Context> context, |
| Local<Module> module) { |
| Isolate* isolate = context->GetIsolate(); |
| |
| std::shared_ptr<ModuleEmbedderData> module_data = |
| GetModuleDataFromContext(context); |
| auto json_value_it = module_data->json_module_to_parsed_json_map.find( |
| Global<Module>(isolate, module)); |
| CHECK(json_value_it != module_data->json_module_to_parsed_json_map.end()); |
| Local<Value> json_value = json_value_it->second.Get(isolate); |
| |
| TryCatch try_catch(isolate); |
| Maybe<bool> result = module->SetSyntheticModuleExport( |
| isolate, |
| String::NewFromUtf8Literal(isolate, "default", |
| NewStringType::kInternalized), |
| json_value); |
| |
| // Setting the default export should never fail. |
| CHECK(!try_catch.HasCaught()); |
| CHECK(!result.IsNothing() && result.FromJust()); |
| |
| Local<Promise::Resolver> resolver = |
| Promise::Resolver::New(context).ToLocalChecked(); |
| resolver->Resolve(context, Undefined(isolate)).ToChecked(); |
| return resolver->GetPromise(); |
| } |
| |
| struct DynamicImportData { |
| DynamicImportData(Isolate* isolate_, Local<Context> context_, |
| Local<Value> referrer_, Local<String> specifier_, |
| Local<FixedArray> import_assertions_, |
| Local<Promise::Resolver> resolver_) |
| : isolate(isolate_) { |
| context.Reset(isolate, context_); |
| referrer.Reset(isolate, referrer_); |
| specifier.Reset(isolate, specifier_); |
| import_assertions.Reset(isolate, import_assertions_); |
| resolver.Reset(isolate, resolver_); |
| } |
| |
| Isolate* isolate; |
| // The initiating context. It can be the Realm created by d8, or the context |
| // created by ShadowRealm built-in. |
| Global<Context> context; |
| Global<Value> referrer; |
| Global<String> specifier; |
| Global<FixedArray> import_assertions; |
| Global<Promise::Resolver> resolver; |
| }; |
| |
| namespace { |
| struct ModuleResolutionData { |
| ModuleResolutionData(Isolate* isolate_, Local<Value> module_namespace_, |
| Local<Promise::Resolver> resolver_) |
| : isolate(isolate_) { |
| module_namespace.Reset(isolate, module_namespace_); |
| resolver.Reset(isolate, resolver_); |
| } |
| |
| Isolate* isolate; |
| Global<Value> module_namespace; |
| Global<Promise::Resolver> resolver; |
| }; |
| |
| } // namespace |
| |
| void Shell::ModuleResolutionSuccessCallback( |
| const FunctionCallbackInfo<Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| std::unique_ptr<ModuleResolutionData> module_resolution_data( |
| static_cast<ModuleResolutionData*>( |
| info.Data().As<v8::External>()->Value())); |
| Isolate* isolate(module_resolution_data->isolate); |
| HandleScope handle_scope(isolate); |
| |
| Local<Promise::Resolver> resolver( |
| module_resolution_data->resolver.Get(isolate)); |
| Local<Value> module_namespace( |
| module_resolution_data->module_namespace.Get(isolate)); |
| |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| Local<Context> realm = data->realms_[data->realm_current_].Get(isolate); |
| Context::Scope context_scope(realm); |
| |
| resolver->Resolve(realm, module_namespace).ToChecked(); |
| } |
| |
| void Shell::ModuleResolutionFailureCallback( |
| const FunctionCallbackInfo<Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| std::unique_ptr<ModuleResolutionData> module_resolution_data( |
| static_cast<ModuleResolutionData*>( |
| info.Data().As<v8::External>()->Value())); |
| Isolate* isolate(module_resolution_data->isolate); |
| HandleScope handle_scope(isolate); |
| |
| Local<Promise::Resolver> resolver( |
| module_resolution_data->resolver.Get(isolate)); |
| |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| Local<Context> realm = data->realms_[data->realm_current_].Get(isolate); |
| Context::Scope context_scope(realm); |
| |
| DCHECK_EQ(info.Length(), 1); |
| resolver->Reject(realm, info[0]).ToChecked(); |
| } |
| |
| MaybeLocal<Promise> Shell::HostImportModuleDynamically( |
| Local<Context> context, Local<Data> host_defined_options, |
| Local<Value> resource_name, Local<String> specifier, |
| Local<FixedArray> import_assertions) { |
| Isolate* isolate = context->GetIsolate(); |
| |
| MaybeLocal<Promise::Resolver> maybe_resolver = |
| Promise::Resolver::New(context); |
| Local<Promise::Resolver> resolver; |
| if (!maybe_resolver.ToLocal(&resolver)) return MaybeLocal<Promise>(); |
| |
| if (!resource_name->IsNull() && |
| !IsValidHostDefinedOptions(context, host_defined_options, |
| resource_name)) { |
| resolver |
| ->Reject(context, v8::Exception::TypeError(String::NewFromUtf8Literal( |
| isolate, "Invalid host defined options"))) |
| .ToChecked(); |
| } else { |
| DynamicImportData* data = |
| new DynamicImportData(isolate, context, resource_name, specifier, |
| import_assertions, resolver); |
| PerIsolateData::Get(isolate)->AddDynamicImportData(data); |
| isolate->EnqueueMicrotask(Shell::DoHostImportModuleDynamically, data); |
| } |
| return resolver->GetPromise(); |
| } |
| |
| void Shell::HostInitializeImportMetaObject(Local<Context> context, |
| Local<Module> module, |
| Local<Object> meta) { |
| Isolate* isolate = context->GetIsolate(); |
| HandleScope handle_scope(isolate); |
| |
| std::shared_ptr<ModuleEmbedderData> module_data = |
| GetModuleDataFromContext(context); |
| auto specifier_it = module_data->module_to_specifier_map.find( |
| Global<Module>(isolate, module)); |
| CHECK(specifier_it != module_data->module_to_specifier_map.end()); |
| |
| Local<String> url_key = |
| String::NewFromUtf8Literal(isolate, "url", NewStringType::kInternalized); |
| Local<String> url = String::NewFromUtf8(isolate, specifier_it->second.c_str()) |
| .ToLocalChecked(); |
| meta->CreateDataProperty(context, url_key, url).ToChecked(); |
| } |
| |
| MaybeLocal<Context> Shell::HostCreateShadowRealmContext( |
| Local<Context> initiator_context) { |
| Local<Context> context = v8::Context::New(initiator_context->GetIsolate()); |
| std::shared_ptr<ModuleEmbedderData> shadow_realm_data = |
| InitializeModuleEmbedderData(context); |
| std::shared_ptr<ModuleEmbedderData> initiator_data = |
| GetModuleDataFromContext(initiator_context); |
| |
| // ShadowRealms are synchronously accessible and are always in the same origin |
| // as the initiator context. |
| context->SetSecurityToken(initiator_context->GetSecurityToken()); |
| shadow_realm_data->origin = initiator_data->origin; |
| |
| return context; |
| } |
| |
| void Shell::DoHostImportModuleDynamically(void* import_data) { |
| DynamicImportData* import_data_ = |
| static_cast<DynamicImportData*>(import_data); |
| |
| Isolate* isolate(import_data_->isolate); |
| HandleScope handle_scope(isolate); |
| |
| Local<Context> realm = import_data_->context.Get(isolate); |
| Local<Value> referrer(import_data_->referrer.Get(isolate)); |
| Local<String> specifier(import_data_->specifier.Get(isolate)); |
| Local<FixedArray> import_assertions( |
| import_data_->import_assertions.Get(isolate)); |
| Local<Promise::Resolver> resolver(import_data_->resolver.Get(isolate)); |
| |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| data->DeleteDynamicImportData(import_data_); |
| |
| Context::Scope context_scope(realm); |
| |
| ModuleType module_type = ModuleEmbedderData::ModuleTypeFromImportAssertions( |
| realm, import_assertions, false); |
| |
| TryCatch try_catch(isolate); |
| try_catch.SetVerbose(true); |
| |
| if (module_type == ModuleType::kInvalid) { |
| ThrowError(isolate, "Invalid module type was asserted"); |
| CHECK(try_catch.HasCaught()); |
| resolver->Reject(realm, try_catch.Exception()).ToChecked(); |
| return; |
| } |
| |
| std::shared_ptr<ModuleEmbedderData> module_data = |
| GetModuleDataFromContext(realm); |
| |
| std::string source_url = referrer->IsNull() |
| ? module_data->origin |
| : ToSTLString(isolate, referrer.As<String>()); |
| std::string dir_name = |
| DirName(NormalizePath(source_url, GetWorkingDirectory())); |
| std::string file_name = ToSTLString(isolate, specifier); |
| std::string absolute_path = NormalizePath(file_name, dir_name); |
| |
| Local<Module> root_module; |
| auto module_it = |
| module_data->module_map.find(std::make_pair(absolute_path, module_type)); |
| if (module_it != module_data->module_map.end()) { |
| root_module = module_it->second.Get(isolate); |
| } else if (!FetchModuleTree(Local<Module>(), realm, absolute_path, |
| module_type) |
| .ToLocal(&root_module)) { |
| CHECK(try_catch.HasCaught()); |
| resolver->Reject(realm, try_catch.Exception()).ToChecked(); |
| return; |
| } |
| |
| MaybeLocal<Value> maybe_result; |
| if (root_module->InstantiateModule(realm, ResolveModuleCallback) |
| .FromMaybe(false)) { |
| maybe_result = root_module->Evaluate(realm); |
| CHECK(!maybe_result.IsEmpty()); |
| EmptyMessageQueues(isolate); |
| } |
| |
| Local<Value> result; |
| if (!maybe_result.ToLocal(&result)) { |
| DCHECK(try_catch.HasCaught()); |
| resolver->Reject(realm, try_catch.Exception()).ToChecked(); |
| return; |
| } |
| |
| Local<Value> module_namespace = root_module->GetModuleNamespace(); |
| Local<Promise> result_promise(result.As<Promise>()); |
| |
| // Setup callbacks, and then chain them to the result promise. |
| // ModuleResolutionData will be deleted by the callbacks. |
| auto module_resolution_data = |
| new ModuleResolutionData(isolate, module_namespace, resolver); |
| Local<v8::External> edata = External::New(isolate, module_resolution_data); |
| Local<Function> callback_success; |
| CHECK(Function::New(realm, ModuleResolutionSuccessCallback, edata) |
| .ToLocal(&callback_success)); |
| Local<Function> callback_failure; |
| CHECK(Function::New(realm, ModuleResolutionFailureCallback, edata) |
| .ToLocal(&callback_failure)); |
| result_promise->Then(realm, callback_success, callback_failure) |
| .ToLocalChecked(); |
| } |
| |
| bool Shell::ExecuteModule(Isolate* isolate, const char* file_name) { |
| HandleScope handle_scope(isolate); |
| |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| Local<Context> realm = data->realms_[data->realm_current_].Get(isolate); |
| Context::Scope context_scope(realm); |
| |
| std::string absolute_path = NormalizePath(file_name, GetWorkingDirectory()); |
| |
| // Use a non-verbose TryCatch and report exceptions manually using |
| // Shell::ReportException, because some errors (such as file errors) are |
| // thrown without entering JS and thus do not trigger |
| // isolate->ReportPendingMessages(). |
| TryCatch try_catch(isolate); |
| |
| std::shared_ptr<ModuleEmbedderData> module_data = |
| GetModuleDataFromContext(realm); |
| Local<Module> root_module; |
| auto module_it = module_data->module_map.find( |
| std::make_pair(absolute_path, ModuleType::kJavaScript)); |
| if (module_it != module_data->module_map.end()) { |
| root_module = module_it->second.Get(isolate); |
| } else if (!FetchModuleTree(Local<Module>(), realm, absolute_path, |
| ModuleType::kJavaScript) |
| .ToLocal(&root_module)) { |
| CHECK(try_catch.HasCaught()); |
| ReportException(isolate, &try_catch); |
| return false; |
| } |
| |
| module_data->origin = absolute_path; |
| |
| MaybeLocal<Value> maybe_result; |
| if (root_module->InstantiateModule(realm, ResolveModuleCallback) |
| .FromMaybe(false)) { |
| maybe_result = root_module->Evaluate(realm); |
| CHECK(!maybe_result.IsEmpty()); |
| EmptyMessageQueues(isolate); |
| } |
| Local<Value> result; |
| if (!maybe_result.ToLocal(&result)) { |
| DCHECK(try_catch.HasCaught()); |
| ReportException(isolate, &try_catch); |
| return false; |
| } |
| |
| // Loop until module execution finishes |
| Local<Promise> result_promise(result.As<Promise>()); |
| while (isolate->HasPendingBackgroundTasks() || |
| (result_promise->State() == Promise::kPending && |
| reinterpret_cast<i::Isolate*>(isolate) |
| ->default_microtask_queue() |
| ->size() > 0)) { |
| Shell::CompleteMessageLoop(isolate); |
| } |
| |
| if (result_promise->State() == Promise::kRejected) { |
| // If the exception has been caught by the promise pipeline, we rethrow |
| // here in order to ReportException. |
| // TODO(cbruni): Clean this up after we create a new API for the case |
| // where TLA is enabled. |
| if (!try_catch.HasCaught()) { |
| isolate->ThrowException(result_promise->Result()); |
| } else { |
| DCHECK_EQ(try_catch.Exception(), result_promise->Result()); |
| } |
| ReportException(isolate, &try_catch); |
| return false; |
| } |
| |
| std::vector<std::tuple<Local<Module>, Local<Message>>> stalled = |
| root_module->GetStalledTopLevelAwaitMessage(isolate); |
| if (stalled.size() > 0) { |
| Local<Message> message = std::get<1>(stalled[0]); |
| ReportException(isolate, message, v8::Exception::Error(message->Get())); |
| return false; |
| } |
| |
| DCHECK(!try_catch.HasCaught()); |
| return true; |
| } |
| |
| // Treat every line as a JSON value and parse it. |
| bool Shell::LoadJSON(Isolate* isolate, const char* file_name) { |
| HandleScope handle_scope(isolate); |
| PerIsolateData* isolate_data = PerIsolateData::Get(isolate); |
| Local<Context> realm = |
| isolate_data->realms_[isolate_data->realm_current_].Get(isolate); |
| Context::Scope context_scope(realm); |
| TryCatch try_catch(isolate); |
| |
| std::string absolute_path = NormalizePath(file_name, GetWorkingDirectory()); |
| int length = 0; |
| std::unique_ptr<char[]> data(ReadChars(absolute_path.c_str(), &length)); |
| if (length == 0) { |
| printf("Error reading '%s'\n", file_name); |
| base::OS::ExitProcess(1); |
| } |
| std::stringstream stream(data.get()); |
| std::string line; |
| while (std::getline(stream, line, '\n')) { |
| for (int r = 0; r < DeserializationRunCount(); ++r) { |
| Local<String> source = |
| String::NewFromUtf8(isolate, line.c_str()).ToLocalChecked(); |
| MaybeLocal<Value> maybe_value = JSON::Parse(realm, source); |
| |
| Local<Value> value; |
| if (!maybe_value.ToLocal(&value)) { |
| DCHECK(try_catch.HasCaught()); |
| ReportException(isolate, &try_catch); |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| PerIsolateData::PerIsolateData(Isolate* isolate) |
| : isolate_(isolate), realms_(nullptr) { |
| isolate->SetData(0, this); |
| if (i::v8_flags.expose_async_hooks) { |
| async_hooks_wrapper_ = new AsyncHooks(isolate); |
| } |
| ignore_unhandled_promises_ = false; |
| } |
| |
| PerIsolateData::~PerIsolateData() { |
| isolate_->SetData(0, nullptr); // Not really needed, just to be sure... |
| if (i::v8_flags.expose_async_hooks) { |
| delete async_hooks_wrapper_; // This uses the isolate |
| } |
| #if defined(LEAK_SANITIZER) |
| for (DynamicImportData* data : import_data_) { |
| delete data; |
| } |
| #endif |
| } |
| |
| void PerIsolateData::SetTimeout(Local<Function> callback, |
| Local<Context> context) { |
| set_timeout_callbacks_.emplace(isolate_, callback); |
| set_timeout_contexts_.emplace(isolate_, context); |
| } |
| |
| MaybeLocal<Function> PerIsolateData::GetTimeoutCallback() { |
| if (set_timeout_callbacks_.empty()) return MaybeLocal<Function>(); |
| Local<Function> result = set_timeout_callbacks_.front().Get(isolate_); |
| set_timeout_callbacks_.pop(); |
| return result; |
| } |
| |
| MaybeLocal<Context> PerIsolateData::GetTimeoutContext() { |
| if (set_timeout_contexts_.empty()) return MaybeLocal<Context>(); |
| Local<Context> result = set_timeout_contexts_.front().Get(isolate_); |
| set_timeout_contexts_.pop(); |
| return result; |
| } |
| |
| void PerIsolateData::RemoveUnhandledPromise(Local<Promise> promise) { |
| if (ignore_unhandled_promises_) return; |
| // Remove handled promises from the list |
| DCHECK_EQ(promise->GetIsolate(), isolate_); |
| for (auto it = unhandled_promises_.begin(); it != unhandled_promises_.end(); |
| ++it) { |
| v8::Local<v8::Promise> unhandled_promise = std::get<0>(*it).Get(isolate_); |
| if (unhandled_promise == promise) { |
| unhandled_promises_.erase(it--); |
| } |
| } |
| } |
| |
| void PerIsolateData::AddUnhandledPromise(Local<Promise> promise, |
| Local<Message> message, |
| Local<Value> exception) { |
| if (ignore_unhandled_promises_) return; |
| DCHECK_EQ(promise->GetIsolate(), isolate_); |
| unhandled_promises_.emplace_back(v8::Global<v8::Promise>(isolate_, promise), |
| v8::Global<v8::Message>(isolate_, message), |
| v8::Global<v8::Value>(isolate_, exception)); |
| } |
| |
| int PerIsolateData::HandleUnhandledPromiseRejections() { |
| // Avoid recursive calls to HandleUnhandledPromiseRejections. |
| if (ignore_unhandled_promises_) return 0; |
| if (isolate_->IsExecutionTerminating()) return 0; |
| ignore_unhandled_promises_ = true; |
| v8::HandleScope scope(isolate_); |
| // Ignore promises that get added during error reporting. |
| size_t i = 0; |
| for (; i < unhandled_promises_.size(); i++) { |
| const auto& tuple = unhandled_promises_[i]; |
| Local<v8::Message> message = std::get<1>(tuple).Get(isolate_); |
| Local<v8::Value> value = std::get<2>(tuple).Get(isolate_); |
| Shell::ReportException(isolate_, message, value); |
| } |
| unhandled_promises_.clear(); |
| ignore_unhandled_promises_ = false; |
| return static_cast<int>(i); |
| } |
| |
| void PerIsolateData::AddDynamicImportData(DynamicImportData* data) { |
| #if defined(LEAK_SANITIZER) |
| import_data_.insert(data); |
| #endif |
| } |
| void PerIsolateData::DeleteDynamicImportData(DynamicImportData* data) { |
| #if defined(LEAK_SANITIZER) |
| import_data_.erase(data); |
| #endif |
| delete data; |
| } |
| |
| Local<FunctionTemplate> PerIsolateData::GetTestApiObjectCtor() const { |
| return test_api_object_ctor_.Get(isolate_); |
| } |
| |
| void PerIsolateData::SetTestApiObjectCtor(Local<FunctionTemplate> ctor) { |
| test_api_object_ctor_.Reset(isolate_, ctor); |
| } |
| |
| Local<FunctionTemplate> PerIsolateData::GetDomNodeCtor() const { |
| return dom_node_ctor_.Get(isolate_); |
| } |
| |
| void PerIsolateData::SetDomNodeCtor(Local<FunctionTemplate> ctor) { |
| dom_node_ctor_.Reset(isolate_, ctor); |
| } |
| |
| PerIsolateData::RealmScope::RealmScope(PerIsolateData* data) : data_(data) { |
| data_->realm_count_ = 1; |
| data_->realm_current_ = 0; |
| data_->realm_switch_ = 0; |
| data_->realms_ = new Global<Context>[1]; |
| data_->realms_[0].Reset(data_->isolate_, |
| data_->isolate_->GetEnteredOrMicrotaskContext()); |
| } |
| |
| PerIsolateData::RealmScope::~RealmScope() { |
| // Drop realms to avoid keeping them alive. |
| data_->realm_count_ = 0; |
| delete[] data_->realms_; |
| } |
| |
| PerIsolateData::ExplicitRealmScope::ExplicitRealmScope(PerIsolateData* data, |
| int index) |
| : data_(data), index_(index) { |
| realm_ = Local<Context>::New(data->isolate_, data->realms_[index_]); |
| realm_->Enter(); |
| previous_index_ = data->realm_current_; |
| data->realm_current_ = data->realm_switch_ = index_; |
| } |
| |
| PerIsolateData::ExplicitRealmScope::~ExplicitRealmScope() { |
| realm_->Exit(); |
| data_->realm_current_ = data_->realm_switch_ = previous_index_; |
| } |
| |
| Local<Context> PerIsolateData::ExplicitRealmScope::context() const { |
| return realm_; |
| } |
| |
| int PerIsolateData::RealmFind(Local<Context> context) { |
| for (int i = 0; i < realm_count_; ++i) { |
| if (realms_[i] == context) return i; |
| } |
| return -1; |
| } |
| |
| int PerIsolateData::RealmIndexOrThrow( |
| const v8::FunctionCallbackInfo<v8::Value>& info, int arg_offset) { |
| if (info.Length() < arg_offset || !info[arg_offset]->IsNumber()) { |
| ThrowError(info.GetIsolate(), "Invalid argument"); |
| return -1; |
| } |
| int index = info[arg_offset] |
| ->Int32Value(info.GetIsolate()->GetCurrentContext()) |
| .FromMaybe(-1); |
| if (index < 0 || index >= realm_count_ || realms_[index].IsEmpty()) { |
| ThrowError(info.GetIsolate(), "Invalid realm index"); |
| return -1; |
| } |
| return index; |
| } |
| |
| // GetTimestamp() returns a time stamp as double, measured in milliseconds. |
| // When v8_flags.verify_predictable mode is enabled it returns result of |
| // v8::Platform::MonotonicallyIncreasingTime(). |
| double Shell::GetTimestamp() { |
| if (i::v8_flags.verify_predictable) { |
| return g_platform->MonotonicallyIncreasingTime(); |
| } else { |
| base::TimeDelta delta = base::TimeTicks::Now() - kInitialTicks; |
| return delta.InMillisecondsF(); |
| } |
| } |
| uint64_t Shell::GetTracingTimestampFromPerformanceTimestamp( |
| double performance_timestamp) { |
| // Don't use this in --verify-predictable mode, predictable timestamps don't |
| // work well with tracing. |
| DCHECK(!i::v8_flags.verify_predictable); |
| base::TimeDelta delta = |
| base::TimeDelta::FromMillisecondsD(performance_timestamp); |
| // See TracingController::CurrentTimestampMicroseconds(). |
| int64_t internal_value = (delta + kInitialTicks).ToInternalValue(); |
| DCHECK_GE(internal_value, 0); |
| return internal_value; |
| } |
| |
| // performance.now() returns GetTimestamp(). |
| void Shell::PerformanceNow(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| info.GetReturnValue().Set(GetTimestamp()); |
| } |
| |
| // performance.mark() records and returns a PerformanceEntry with the current |
| // timestamp. |
| void Shell::PerformanceMark(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| Local<Context> context = isolate->GetCurrentContext(); |
| |
| if (info.Length() < 1 || !info[0]->IsString()) { |
| ThrowError(info.GetIsolate(), "Invalid 'name' argument"); |
| return; |
| } |
| Local<String> name = info[0].As<String>(); |
| |
| double timestamp = GetTimestamp(); |
| |
| Local<Object> performance_entry = Object::New(isolate); |
| performance_entry |
| ->DefineOwnProperty(context, |
| String::NewFromUtf8Literal(isolate, "entryType"), |
| String::NewFromUtf8Literal(isolate, "mark"), ReadOnly) |
| .Check(); |
| performance_entry |
| ->DefineOwnProperty(context, String::NewFromUtf8Literal(isolate, "name"), |
| name, ReadOnly) |
| .Check(); |
| performance_entry |
| ->DefineOwnProperty(context, |
| String::NewFromUtf8Literal(isolate, "startTime"), |
| Number::New(isolate, timestamp), ReadOnly) |
| .Check(); |
| performance_entry |
| ->DefineOwnProperty(context, |
| String::NewFromUtf8Literal(isolate, "duration"), |
| Integer::New(isolate, 0), ReadOnly) |
| .Check(); |
| |
| info.GetReturnValue().Set(performance_entry); |
| } |
| |
| // performance.measure() records and returns a PerformanceEntry with a duration |
| // since a given mark, or since zero. |
| void Shell::PerformanceMeasure( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| Local<Context> context = isolate->GetCurrentContext(); |
| |
| if (info.Length() < 1 || !info[0]->IsString()) { |
| ThrowError(info.GetIsolate(), "Invalid 'name' argument"); |
| return; |
| } |
| v8::Local<String> name = info[0].As<String>(); |
| |
| double start_timestamp = 0; |
| if (info.Length() >= 2) { |
| Local<Value> start_mark = info[1].As<Value>(); |
| if (!start_mark->IsObject()) { |
| ThrowError(info.GetIsolate(), |
| "Invalid 'startMark' argument: Not an Object"); |
| return; |
| } |
| Local<Value> start_time_field; |
| if (!start_mark.As<Object>() |
| ->Get(context, String::NewFromUtf8Literal(isolate, "startTime")) |
| .ToLocal(&start_time_field)) { |
| return; |
| } |
| if (!start_time_field->IsNumber()) { |
| ThrowError(info.GetIsolate(), |
| "Invalid 'startMark' argument: No numeric 'startTime' field"); |
| return; |
| } |
| start_timestamp = start_time_field.As<Number>()->Value(); |
| } |
| if (info.Length() > 2) { |
| ThrowError(info.GetIsolate(), "Too many arguments"); |
| return; |
| } |
| |
| double end_timestamp = GetTimestamp(); |
| |
| if (options.trace_enabled) { |
| size_t hash = base::hash_combine(name->GetIdentityHash(), start_timestamp, |
| end_timestamp); |
| |
| String::Utf8Value utf8(isolate, name); |
| TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( |
| "v8", *utf8, static_cast<uint64_t>(hash), |
| GetTracingTimestampFromPerformanceTimestamp(start_timestamp), |
| "startTime", start_timestamp); |
| TRACE_EVENT_COPY_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( |
| "v8", *utf8, static_cast<uint64_t>(hash), |
| GetTracingTimestampFromPerformanceTimestamp(end_timestamp)); |
| } |
| |
| Local<Object> performance_entry = Object::New(isolate); |
| performance_entry |
| ->DefineOwnProperty( |
| context, String::NewFromUtf8Literal(isolate, "entryType"), |
| String::NewFromUtf8Literal(isolate, "measure"), ReadOnly) |
| .Check(); |
| performance_entry |
| ->DefineOwnProperty(context, String::NewFromUtf8Literal(isolate, "name"), |
| name, ReadOnly) |
| .Check(); |
| performance_entry |
| ->DefineOwnProperty(context, |
| String::NewFromUtf8Literal(isolate, "startTime"), |
| Number::New(isolate, start_timestamp), ReadOnly) |
| .Check(); |
| performance_entry |
| ->DefineOwnProperty( |
| context, String::NewFromUtf8Literal(isolate, "duration"), |
| Number::New(isolate, end_timestamp - start_timestamp), ReadOnly) |
| .Check(); |
| |
| info.GetReturnValue().Set(performance_entry); |
| } |
| |
| // performance.measureMemory() implements JavaScript Memory API proposal. |
| // See https://github.com/ulan/javascript-agent-memory/blob/master/explainer.md. |
| void Shell::PerformanceMeasureMemory( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| v8::MeasureMemoryMode mode = v8::MeasureMemoryMode::kSummary; |
| v8::Isolate* isolate = info.GetIsolate(); |
| Local<Context> context = isolate->GetCurrentContext(); |
| if (info.Length() >= 1 && info[0]->IsObject()) { |
| Local<Object> object = info[0].As<Object>(); |
| Local<Value> value = TryGetValue(isolate, context, object, "detailed") |
| .FromMaybe(Local<Value>()); |
| if (value.IsEmpty()) { |
| // Exception was thrown and scheduled, so return from the callback. |
| return; |
| } |
| if (value->IsBoolean() && value->BooleanValue(isolate)) { |
| mode = v8::MeasureMemoryMode::kDetailed; |
| } |
| } |
| Local<v8::Promise::Resolver> promise_resolver = |
| v8::Promise::Resolver::New(context).ToLocalChecked(); |
| info.GetIsolate()->MeasureMemory( |
| v8::MeasureMemoryDelegate::Default(isolate, context, promise_resolver, |
| mode), |
| v8::MeasureMemoryExecution::kEager); |
| info.GetReturnValue().Set(promise_resolver->GetPromise()); |
| } |
| |
| // Realm.current() returns the index of the currently active realm. |
| void Shell::RealmCurrent(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| int index = data->RealmFind(isolate->GetEnteredOrMicrotaskContext()); |
| if (index == -1) return; |
| info.GetReturnValue().Set(index); |
| } |
| |
| // Realm.owner(o) returns the index of the realm that created o. |
| void Shell::RealmOwner(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| if (info.Length() < 1 || !info[0]->IsObject()) { |
| ThrowError(info.GetIsolate(), "Invalid argument"); |
| return; |
| } |
| Local<Object> object = |
| info[0]->ToObject(isolate->GetCurrentContext()).ToLocalChecked(); |
| i::Handle<i::JSReceiver> i_object = Utils::OpenHandle(*object); |
| if (i_object->IsJSGlobalProxy() && |
| i::Handle<i::JSGlobalProxy>::cast(i_object)->IsDetached()) { |
| return; |
| } |
| Local<Context> creation_context; |
| if (!object->GetCreationContext().ToLocal(&creation_context)) { |
| ThrowError(info.GetIsolate(), "object doesn't have creation context"); |
| return; |
| } |
| int index = data->RealmFind(creation_context); |
| if (index == -1) return; |
| info.GetReturnValue().Set(index); |
| } |
| |
| // Realm.global(i) returns the global object of realm i. |
| // (Note that properties of global objects cannot be read/written cross-realm.) |
| void Shell::RealmGlobal(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| PerIsolateData* data = PerIsolateData::Get(info.GetIsolate()); |
| int index = data->RealmIndexOrThrow(info, 0); |
| if (index == -1) return; |
| // TODO(chromium:324812): Ideally Context::Global should never return raw |
| // global objects but return a global proxy. Currently it returns global |
| // object when the global proxy is detached from the global object. The |
| // following is a workaround till we fix Context::Global so we don't leak |
| // global objects. |
| Local<Object> global = |
| Local<Context>::New(info.GetIsolate(), data->realms_[index])->Global(); |
| i::Handle<i::Object> i_global = Utils::OpenHandle(*global); |
| if (i_global->IsJSGlobalObject()) { |
| i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(info.GetIsolate()); |
| i::Handle<i::JSObject> i_global_proxy = |
| handle(i::Handle<i::JSGlobalObject>::cast(i_global)->global_proxy(), |
| i_isolate); |
| global = Utils::ToLocal(i_global_proxy); |
| } |
| info.GetReturnValue().Set(global); |
| } |
| |
| MaybeLocal<Context> Shell::CreateRealm( |
| const v8::FunctionCallbackInfo<v8::Value>& info, int index, |
| v8::MaybeLocal<Value> global_object) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| const char* kGlobalHandleLabel = "d8::realm"; |
| Isolate* isolate = info.GetIsolate(); |
| TryCatch try_catch(isolate); |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| if (index < 0) { |
| Global<Context>* old_realms = data->realms_; |
| index = data->realm_count_; |
| data->realms_ = new Global<Context>[++data->realm_count_]; |
| for (int i = 0; i < index; ++i) { |
| Global<Context>& realm = data->realms_[i]; |
| realm.Reset(isolate, old_realms[i]); |
| if (!realm.IsEmpty()) { |
| realm.AnnotateStrongRetainer(kGlobalHandleLabel); |
| } |
| old_realms[i].Reset(); |
| } |
| delete[] old_realms; |
| } |
| Local<ObjectTemplate> global_template = CreateGlobalTemplate(isolate); |
| Local<Context> context = |
| Context::New(isolate, nullptr, global_template, global_object); |
| if (context.IsEmpty()) return MaybeLocal<Context>(); |
| DCHECK(!try_catch.HasCaught()); |
| InitializeModuleEmbedderData(context); |
| data->realms_[index].Reset(isolate, context); |
| data->realms_[index].AnnotateStrongRetainer(kGlobalHandleLabel); |
| info.GetReturnValue().Set(index); |
| return context; |
| } |
| |
| void Shell::DisposeRealm(const v8::FunctionCallbackInfo<v8::Value>& info, |
| int index) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| Local<Context> context = data->realms_[index].Get(isolate); |
| data->realms_[index].Reset(); |
| // ContextDisposedNotification expects the disposed context to be entered. |
| v8::Context::Scope scope(context); |
| isolate->ContextDisposedNotification(); |
| } |
| |
| // Realm.create() creates a new realm with a distinct security token |
| // and returns its index. |
| void Shell::RealmCreate(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| CreateRealm(info, -1, v8::MaybeLocal<Value>()); |
| } |
| |
| // Realm.createAllowCrossRealmAccess() creates a new realm with the same |
| // security token as the current realm. |
| void Shell::RealmCreateAllowCrossRealmAccess( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Local<Context> context; |
| if (CreateRealm(info, -1, v8::MaybeLocal<Value>()).ToLocal(&context)) { |
| context->SetSecurityToken( |
| info.GetIsolate()->GetEnteredOrMicrotaskContext()->GetSecurityToken()); |
| } |
| } |
| |
| // Realm.navigate(i) creates a new realm with a distinct security token |
| // in place of realm i. |
| void Shell::RealmNavigate(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| int index = data->RealmIndexOrThrow(info, 0); |
| if (index == -1) return; |
| if (index == 0 || index == data->realm_current_ || |
| index == data->realm_switch_) { |
| ThrowError(info.GetIsolate(), "Invalid realm index"); |
| return; |
| } |
| |
| Local<Context> context = Local<Context>::New(isolate, data->realms_[index]); |
| v8::MaybeLocal<Value> global_object = context->Global(); |
| |
| // Context::Global doesn't return JSGlobalProxy if DetachGlobal is called in |
| // advance. |
| if (!global_object.IsEmpty()) { |
| HandleScope scope(isolate); |
| if (!Utils::OpenHandle(*global_object.ToLocalChecked()) |
| ->IsJSGlobalProxy()) { |
| global_object = v8::MaybeLocal<Value>(); |
| } |
| } |
| |
| DisposeRealm(info, index); |
| CreateRealm(info, index, global_object); |
| } |
| |
| // Realm.detachGlobal(i) detaches the global objects of realm i from realm i. |
| void Shell::RealmDetachGlobal(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| int index = data->RealmIndexOrThrow(info, 0); |
| if (index == -1) return; |
| if (index == 0 || index == data->realm_current_ || |
| index == data->realm_switch_) { |
| ThrowError(info.GetIsolate(), "Invalid realm index"); |
| return; |
| } |
| |
| HandleScope scope(isolate); |
| Local<Context> realm = Local<Context>::New(isolate, data->realms_[index]); |
| realm->DetachGlobal(); |
| } |
| |
| // Realm.dispose(i) disposes the reference to the realm i. |
| void Shell::RealmDispose(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| int index = data->RealmIndexOrThrow(info, 0); |
| if (index == -1) return; |
| if (index == 0 || index == data->realm_current_ || |
| index == data->realm_switch_) { |
| ThrowError(info.GetIsolate(), "Invalid realm index"); |
| return; |
| } |
| DisposeRealm(info, index); |
| } |
| |
| // Realm.switch(i) switches to the realm i for consecutive interactive inputs. |
| void Shell::RealmSwitch(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| int index = data->RealmIndexOrThrow(info, 0); |
| if (index == -1) return; |
| data->realm_switch_ = index; |
| } |
| |
| // Realm.eval(i, s) evaluates s in realm i and returns the result. |
| void Shell::RealmEval(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| int index = data->RealmIndexOrThrow(info, 0); |
| if (index == -1) return; |
| if (info.Length() < 2) { |
| ThrowError(isolate, "Invalid argument"); |
| return; |
| } |
| |
| Local<String> source; |
| if (!ReadSource(info, 1, CodeType::kString).ToLocal(&source)) { |
| ThrowError(isolate, "Invalid argument"); |
| return; |
| } |
| ScriptOrigin origin = |
| CreateScriptOrigin(isolate, String::NewFromUtf8Literal(isolate, "(d8)"), |
| ScriptType::kClassic); |
| |
| ScriptCompiler::Source script_source(source, origin); |
| Local<UnboundScript> script; |
| if (!ScriptCompiler::CompileUnboundScript(isolate, &script_source) |
| .ToLocal(&script)) { |
| return; |
| } |
| Local<Value> result; |
| { |
| PerIsolateData::ExplicitRealmScope realm_scope(data, index); |
| if (!script->BindToCurrentContext() |
| ->Run(realm_scope.context()) |
| .ToLocal(&result)) { |
| return; |
| } |
| } |
| info.GetReturnValue().Set(result); |
| } |
| |
| // Realm.shared is an accessor for a single shared value across realms. |
| void Shell::RealmSharedGet(Local<String> property, |
| const PropertyCallbackInfo<Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| if (data->realm_shared_.IsEmpty()) return; |
| info.GetReturnValue().Set(data->realm_shared_); |
| } |
| |
| void Shell::RealmSharedSet(Local<String> property, Local<Value> value, |
| const PropertyCallbackInfo<void>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| PerIsolateData* data = PerIsolateData::Get(isolate); |
| data->realm_shared_.Reset(isolate, value); |
| } |
| |
| void Shell::LogGetAndStop(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| HandleScope handle_scope(isolate); |
| |
| std::string file_name = i_isolate->v8_file_logger()->file_name(); |
| if (!i::LogFile::IsLoggingToTemporaryFile(file_name)) { |
| ThrowError(isolate, "Only capturing from temporary files is supported."); |
| return; |
| } |
| if (!i_isolate->v8_file_logger()->is_logging()) { |
| ThrowError(isolate, "Logging not enabled."); |
| return; |
| } |
| |
| std::string raw_log; |
| FILE* log_file = i_isolate->v8_file_logger()->TearDownAndGetLogFile(); |
| if (!log_file) { |
| ThrowError(isolate, "Log file does not exist."); |
| return; |
| } |
| |
| bool exists = false; |
| raw_log = i::ReadFile(log_file, &exists, true); |
| base::Fclose(log_file); |
| |
| if (!exists) { |
| ThrowError(isolate, "Unable to read log file."); |
| return; |
| } |
| Local<String> result = |
| String::NewFromUtf8(isolate, raw_log.c_str(), NewStringType::kNormal, |
| static_cast<int>(raw_log.size())) |
| .ToLocalChecked(); |
| |
| info.GetReturnValue().Set(result); |
| } |
| |
| void Shell::TestVerifySourcePositions( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| Isolate* isolate = info.GetIsolate(); |
| // Check if the argument is a valid function. |
| if (info.Length() != 1) { |
| ThrowError(isolate, "Expected function as single argument."); |
| return; |
| } |
| auto arg_handle = Utils::OpenHandle(*info[0]); |
| if (!arg_handle->IsHeapObject() || |
| !i::Handle<i::HeapObject>::cast(arg_handle) |
| ->IsJSFunctionOrBoundFunctionOrWrappedFunction()) { |
| ThrowError(isolate, "Expected function as single argument."); |
| return; |
| } |
| |
| i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| HandleScope handle_scope(isolate); |
| |
| auto callable = |
| i::Handle<i::JSFunctionOrBoundFunctionOrWrappedFunction>::cast( |
| arg_handle); |
| while (callable->IsJSBoundFunction()) { |
| internal::DisallowGarbageCollection no_gc; |
| auto bound_function = i::Handle<i::JSBoundFunction>::cast(callable); |
| auto bound_target = bound_function->bound_target_function(); |
| if (!bound_target.IsJSFunctionOrBoundFunctionOrWrappedFunction()) { |
| internal::AllowGarbageCollection allow_gc; |
| ThrowError(isolate, "Expected function as bound target."); |
| return; |
| } |
| callable = handle( |
| i::JSFunctionOrBoundFunctionOrWrappedFunction::cast(bound_target), |
| i_isolate); |
| } |
| |
| i::Handle<i::JSFunction> function = i::Handle<i::JSFunction>::cast(callable); |
| if (!function->shared().HasBytecodeArray()) { |
| ThrowError(isolate, "Function has no BytecodeArray attached."); |
| return; |
| } |
| i::Handle<i::BytecodeArray> bytecodes = |
| handle(function->shared().GetBytecodeArray(i_isolate), i_isolate); |
| i::interpreter::BytecodeArrayIterator bytecode_iterator(bytecodes); |
| bool has_baseline = function->shared().HasBaselineCode(); |
| i::Handle<i::ByteArray> bytecode_offsets; |
| std::unique_ptr<i::baseline::BytecodeOffsetIterator> offset_iterator; |
| if (has_baseline) { |
| bytecode_offsets = handle( |
| i::ByteArray::cast( |
| function->shared().GetCode(i_isolate).bytecode_offset_table()), |
| i_isolate); |
| offset_iterator = std::make_unique<i::baseline::BytecodeOffsetIterator>( |
| bytecode_offsets, bytecodes); |
| // A freshly initiated BytecodeOffsetIterator points to the prologue. |
| DCHECK_EQ(offset_iterator->current_pc_start_offset(), 0); |
| DCHECK_EQ(offset_iterator->current_bytecode_offset(), |
| i::kFunctionEntryBytecodeOffset); |
| offset_iterator->Advance(); |
| } |
| while (!bytecode_iterator.done()) { |
| if (has_baseline) { |
| if (offset_iterator->current_bytecode_offset() != |
| bytecode_iterator.current_offset()) { |
| ThrowError(isolate, "Baseline bytecode offset mismatch."); |
| return; |
| } |
| // Check that we map every address to this bytecode correctly. |
| // The start address is exclusive and the end address inclusive. |
| for (i::Address pc = offset_iterator->current_pc_start_offset() + 1; |
| pc <= offset_iterator->current_pc_end_offset(); ++pc) { |
| i::baseline::BytecodeOffsetIterator pc_lookup(bytecode_offsets, |
| bytecodes); |
| pc_lookup.AdvanceToPCOffset(pc); |
| if (pc_lookup.current_bytecode_offset() != |
| bytecode_iterator.current_offset()) { |
| ThrowError(isolate, |
| "Baseline bytecode offset mismatch for PC lookup."); |
| return; |
| } |
| } |
| } |
| bytecode_iterator.Advance(); |
| if (has_baseline && !bytecode_iterator.done()) { |
| if (offset_iterator->done()) { |
| ThrowError(isolate, "Missing bytecode(s) in baseline offset mapping."); |
| return; |
| } |
| offset_iterator->Advance(); |
| } |
| } |
| if (has_baseline && !offset_iterator->done()) { |
| ThrowError(isolate, "Excess offsets in baseline offset mapping."); |
| return; |
| } |
| } |
| |
| void Shell::InstallConditionalFeatures( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| Isolate* isolate = info.GetIsolate(); |
| isolate->InstallConditionalFeatures(isolate->GetCurrentContext()); |
| } |
| |
| // async_hooks.createHook() registers functions to be called for different |
| // lifetime events of each async operation. |
| void Shell::AsyncHooksCreateHook( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| Local<Object> wrap = |
| PerIsolateData::Get(info.GetIsolate())->GetAsyncHooks()->CreateHook(info); |
| info.GetReturnValue().Set(wrap); |
| } |
| |
| // async_hooks.executionAsyncId() returns the asyncId of the current execution |
| // context. |
| void Shell::AsyncHooksExecutionAsyncId( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| Isolate* isolate = info.GetIsolate(); |
| HandleScope handle_scope(isolate); |
| info.GetReturnValue().Set(v8::Number::New( |
| isolate, |
| PerIsolateData::Get(isolate)->GetAsyncHooks()->GetExecutionAsyncId())); |
| } |
| |
| void Shell::AsyncHooksTriggerAsyncId( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| Isolate* isolate = info.GetIsolate(); |
| HandleScope handle_scope(isolate); |
| info.GetReturnValue().Set(v8::Number::New( |
| isolate, |
| PerIsolateData::Get(isolate)->GetAsyncHooks()->GetTriggerAsyncId())); |
| } |
| |
| static v8::debug::DebugDelegate dummy_delegate; |
| |
| void Shell::EnableDebugger(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| v8::debug::SetDebugDelegate(info.GetIsolate(), &dummy_delegate); |
| } |
| |
| void Shell::DisableDebugger(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| v8::debug::SetDebugDelegate(info.GetIsolate(), nullptr); |
| } |
| |
| void Shell::SetPromiseHooks(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| if (i::v8_flags.correctness_fuzzer_suppressions) { |
| // Setting promise hoooks dynamically has unexpected timing side-effects |
| // with certain promise optimizations. We might not get all callbacks for |
| // previously scheduled Promises or optimized code-paths that skip Promise |
| // creation. |
| ThrowError(isolate, |
| "d8.promise.setHooks is disabled with " |
| "--correctness-fuzzer-suppressions"); |
| return; |
| } |
| #ifdef V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS |
| Local<Context> context = isolate->GetCurrentContext(); |
| HandleScope handle_scope(isolate); |
| |
| context->SetPromiseHooks( |
| info[0]->IsFunction() ? info[0].As<Function>() : Local<Function>(), |
| info[1]->IsFunction() ? info[1].As<Function>() : Local<Function>(), |
| info[2]->IsFunction() ? info[2].As<Function>() : Local<Function>(), |
| info[3]->IsFunction() ? info[3].As<Function>() : Local<Function>()); |
| |
| info.GetReturnValue().Set(v8::Undefined(isolate)); |
| #else // V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS |
| ThrowError(isolate, |
| "d8.promise.setHooks is disabled due to missing build flag " |
| "v8_enabale_javascript_in_promise_hooks"); |
| #endif // V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS |
| } |
| |
| void Shell::SerializerSerialize( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| HandleScope handle_scope(isolate); |
| Local<Context> context = isolate->GetCurrentContext(); |
| |
| ValueSerializer serializer(isolate); |
| serializer.WriteHeader(); |
| for (int i = 0; i < info.Length(); i++) { |
| bool ok; |
| if (!serializer.WriteValue(context, info[i]).To(&ok)) return; |
| } |
| Local<v8::ArrayBuffer> buffer; |
| { |
| std::pair<uint8_t*, size_t> pair = serializer.Release(); |
| buffer = ArrayBuffer::New(isolate, pair.second); |
| memcpy(buffer->GetBackingStore()->Data(), pair.first, pair.second); |
| free(pair.first); |
| } |
| info.GetReturnValue().Set(buffer); |
| } |
| |
| void Shell::SerializerDeserialize( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| HandleScope handle_scope(isolate); |
| Local<Context> context = isolate->GetCurrentContext(); |
| |
| if (!info[0]->IsArrayBuffer()) { |
| ThrowError(isolate, "Can only deserialize from an ArrayBuffer"); |
| return; |
| } |
| std::shared_ptr<BackingStore> backing_store = |
| info[0].As<ArrayBuffer>()->GetBackingStore(); |
| ValueDeserializer deserializer( |
| isolate, static_cast<const uint8_t*>(backing_store->Data()), |
| backing_store->ByteLength()); |
| bool ok; |
| if (!deserializer.ReadHeader(context).To(&ok)) return; |
| Local<Value> result; |
| if (!deserializer.ReadValue(context).ToLocal(&result)) return; |
| info.GetReturnValue().Set(result); |
| } |
| |
| void Shell::ProfilerSetOnProfileEndListener( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| HandleScope handle_scope(isolate); |
| if (!info[0]->IsFunction()) { |
| ThrowError(isolate, "The OnProfileEnd listener has to be a function"); |
| return; |
| } |
| base::MutexGuard lock_guard(&profiler_end_callback_lock_); |
| profiler_end_callback_[isolate] = |
| std::make_pair(Global<Function>(isolate, info[0].As<Function>()), |
| Global<Context>(isolate, isolate->GetCurrentContext())); |
| } |
| |
| bool Shell::HasOnProfileEndListener(Isolate* isolate) { |
| base::MutexGuard lock_guard(&profiler_end_callback_lock_); |
| return profiler_end_callback_.find(isolate) != profiler_end_callback_.end(); |
| } |
| |
| void Shell::ResetOnProfileEndListener(Isolate* isolate) { |
| // If the inspector is enabled, then the installed console is not the |
| // D8Console. |
| if (options.enable_inspector) return; |
| { |
| base::MutexGuard lock_guard(&profiler_end_callback_lock_); |
| profiler_end_callback_.erase(isolate); |
| } |
| |
| i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| D8Console* console = |
| reinterpret_cast<D8Console*>(i_isolate->console_delegate()); |
| if (console) { |
| console->DisposeProfiler(); |
| } |
| } |
| |
| void Shell::ProfilerTriggerSample( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| Isolate* isolate = info.GetIsolate(); |
| i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| D8Console* console = |
| reinterpret_cast<D8Console*>(i_isolate->console_delegate()); |
| if (console && console->profiler()) { |
| console->profiler()->CollectSample(isolate); |
| } |
| } |
| |
| void Shell::TriggerOnProfileEndListener(Isolate* isolate, std::string profile) { |
| CHECK(HasOnProfileEndListener(isolate)); |
| Local<Function> callback; |
| Local<Context> context; |
| Local<Value> argv[1] = { |
| String::NewFromUtf8(isolate, profile.c_str()).ToLocalChecked()}; |
| { |
| base::MutexGuard lock_guard(&profiler_end_callback_lock_); |
| auto& callback_pair = profiler_end_callback_[isolate]; |
| callback = callback_pair.first.Get(isolate); |
| context = callback_pair.second.Get(isolate); |
| } |
| TryCatch try_catch(isolate); |
| try_catch.SetVerbose(true); |
| USE(callback->Call(context, Undefined(isolate), 1, argv)); |
| } |
| |
| void WriteToFile(FILE* file, const v8::FunctionCallbackInfo<v8::Value>& info, |
| int first_arg_index = 0) { |
| for (int i = first_arg_index; i < info.Length(); i++) { |
| HandleScope handle_scope(info.GetIsolate()); |
| if (i != first_arg_index) { |
| fprintf(file, " "); |
| } |
| |
| // Explicitly catch potential exceptions in toString(). |
| v8::TryCatch try_catch(info.GetIsolate()); |
| Local<Value> arg = info[i]; |
| Local<String> str_obj; |
| |
| if (arg->IsSymbol()) { |
| arg = arg.As<Symbol>()->Description(info.GetIsolate()); |
| } |
| if (!arg->ToString(info.GetIsolate()->GetCurrentContext()) |
| .ToLocal(&str_obj)) { |
| try_catch.ReThrow(); |
| return; |
| } |
| |
| v8::String::Utf8Value str(info.GetIsolate(), str_obj); |
| int n = static_cast<int>(fwrite(*str, sizeof(**str), str.length(), file)); |
| if (n != str.length()) { |
| printf("Error in fwrite\n"); |
| base::OS::ExitProcess(1); |
| } |
| } |
| } |
| |
| void WriteAndFlush(FILE* file, |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| WriteToFile(file, info); |
| fprintf(file, "\n"); |
| fflush(file); |
| } |
| |
| void Shell::Print(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| WriteAndFlush(stdout, info); |
| } |
| |
| void Shell::PrintErr(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| WriteAndFlush(stderr, info); |
| } |
| |
| void Shell::WriteStdout(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| WriteToFile(stdout, info); |
| } |
| |
| // There are two overloads of writeFile(). |
| // |
| // The first parameter is always the filename. |
| // |
| // If there are exactly 2 arguments, and the second argument is an ArrayBuffer |
| // or an ArrayBufferView, write the binary contents into the file. |
| // |
| // Otherwise, convert arguments to UTF-8 strings, and write them to the file, |
| // separated by space. |
| void Shell::WriteFile(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| String::Utf8Value file_name(info.GetIsolate(), info[0]); |
| if (*file_name == nullptr) { |
| ThrowError(info.GetIsolate(), "Error converting filename to string"); |
| return; |
| } |
| FILE* file; |
| if (info.Length() == 2 && |
| (info[1]->IsArrayBuffer() || info[1]->IsArrayBufferView())) { |
| file = base::Fopen(*file_name, "wb"); |
| if (file == nullptr) { |
| ThrowError(info.GetIsolate(), "Error opening file"); |
| return; |
| } |
| |
| void* data; |
| size_t length; |
| if (info[1]->IsArrayBuffer()) { |
| Local<v8::ArrayBuffer> buffer = Local<v8::ArrayBuffer>::Cast(info[1]); |
| length = buffer->ByteLength(); |
| data = buffer->Data(); |
| } else { |
| Local<v8::ArrayBufferView> buffer_view = |
| Local<v8::ArrayBufferView>::Cast(info[1]); |
| length = buffer_view->ByteLength(); |
| data = static_cast<uint8_t*>(buffer_view->Buffer()->Data()) + |
| buffer_view->ByteOffset(); |
| } |
| fwrite(data, 1, length, file); |
| } else { |
| file = base::Fopen(*file_name, "w"); |
| if (file == nullptr) { |
| ThrowError(info.GetIsolate(), "Error opening file"); |
| return; |
| } |
| WriteToFile(file, info, 1); |
| } |
| base::Fclose(file); |
| } |
| |
| void Shell::ReadFile(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| String::Utf8Value file_name(info.GetIsolate(), info[0]); |
| if (*file_name == nullptr) { |
| ThrowError(info.GetIsolate(), "Error converting filename to string"); |
| return; |
| } |
| if (info.Length() == 2) { |
| String::Utf8Value format(info.GetIsolate(), info[1]); |
| if (*format && std::strcmp(*format, "binary") == 0) { |
| ReadBuffer(info); |
| return; |
| } |
| } |
| Local<String> source; |
| if (!ReadFile(info.GetIsolate(), *file_name).ToLocal(&source)) return; |
| info.GetReturnValue().Set(source); |
| } |
| |
| Local<String> Shell::ReadFromStdin(Isolate* isolate) { |
| static const int kBufferSize = 256; |
| char buffer[kBufferSize]; |
| Local<String> accumulator = String::NewFromUtf8Literal(isolate, ""); |
| int length; |
| // Flush stdout before reading stdin, as stdout isn't guaranteed to be flushed |
| // automatically. |
| fflush(stdout); |
| while (true) { |
| // Continue reading if the line ends with an escape '\\' or the line has |
| // not been fully read into the buffer yet (does not end with '\n'). |
| // If fgets gets an error, just give up. |
| char* input = nullptr; |
| input = fgets(buffer, kBufferSize, stdin); |
| if (input == nullptr) return Local<String>(); |
| length = static_cast<int>(strlen(buffer)); |
| if (length == 0) { |
| return accumulator; |
| } else if (buffer[length - 1] != '\n') { |
| accumulator = String::Concat( |
| isolate, accumulator, |
| String::NewFromUtf8(isolate, buffer, NewStringType::kNormal, length) |
| .ToLocalChecked()); |
| } else if (length > 1 && buffer[length - 2] == '\\') { |
| buffer[length - 2] = '\n'; |
| accumulator = |
| String::Concat(isolate, accumulator, |
| String::NewFromUtf8(isolate, buffer, |
| NewStringType::kNormal, length - 1) |
| .ToLocalChecked()); |
| } else { |
| return String::Concat( |
| isolate, accumulator, |
| String::NewFromUtf8(isolate, buffer, NewStringType::kNormal, |
| length - 1) |
| .ToLocalChecked()); |
| } |
| } |
| } |
| |
| void Shell::ExecuteFile(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| DCHECK(i::ValidateCallbackInfo(info)); |
| Isolate* isolate = info.GetIsolate(); |
| for (int i = 0; i < info.Length(); i++) { |
| HandleScope handle_scope(isolate); |
| |