// 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 <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include <algorithm>
#include <fstream>
#include <iomanip>
#include <iterator>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>

#ifdef ENABLE_VTUNE_JIT_INTERFACE
#include "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/fpu.h"
#include "src/base/hashing.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-inl.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/js-array.h"
#include "src/objects/managed-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/objects.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/parsing.h"
#include "src/parsing/scanner-character-streams.h"
#include "src/profiler/profile-generator.h"
#include "src/snapshot/snapshot.h"
#include "src/strings/owning-external-string-resource.h"
#include "src/tasks/cancelable-task.h"
#include "src/tracing/perfetto-sdk.h"
#include "src/utils/ostreams.h"
#include "src/utils/utils.h"

#ifdef V8_OS_DARWIN
#include <mach/mach.h>
#include <mach/task_policy.h>
#endif

#ifdef V8_ENABLE_MAGLEV
#include "src/maglev/maglev-concurrent-dispatcher.h"
#endif  // V8_ENABLE_MAGLEV

#ifdef V8_ENABLE_PARTITION_ALLOC
#include <partition_alloc/partition_root.h>
#include <partition_alloc/shim/allocator_shim_default_dispatch_to_partition_alloc.h>
#endif  // V8_ENABLE_PARTITION_ALLOC

#if V8_OS_POSIX
#include <signal.h>
#endif  // V8_OS_POSIX

#ifdef V8_FUZZILLI
#include "src/fuzzilli/cov.h"
#include "src/fuzzilli/fuzzilli.h"
#endif  // V8_FUZZILLI

#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(V8_OS_WIN)
#include <windows.h>
#else
#include <unistd.h>
#endif  // defined(V8_OS_WIN)

#if V8_ENABLE_WEBASSEMBLY
#include "src/trap-handler/trap-handler.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-js.h"
#include "src/wasm/wasm-result.h"
#include "src/wasm/wasm-serialization.h"
#endif  // V8_ENABLE_WEBASSEMBLY

#ifndef DCHECK
#define DCHECK(condition) assert(condition)
#endif

#ifndef CHECK
#define CHECK(condition) assert(condition)
#endif

namespace v8 {

namespace {

// Set on worker threads to the current Worker instance.
thread_local Worker* current_worker_ = nullptr;

constexpr v8::EmbedderDataTypeTag kInspectorClientTag = 1;

#ifdef V8_FUZZILLI
bool fuzzilli_reprl = true;
#else
bool fuzzilli_reprl = false;
#endif  // V8_FUZZILLI

Isolate::CreateParams GetDefaultIsolateCreateParams() {
  Isolate::CreateParams create_params;
  create_params.constraints.ConfigureDefaults(
      base::SysInfo::AmountOfPhysicalMemory(),
      base::SysInfo::AmountOfVirtualMemory());
  create_params.array_buffer_allocator = Shell::array_buffer_allocator;
  return create_params;
}

// Base class for shell ArrayBuffer allocators. It forwards all operations 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);
  }

  PageAllocator* GetPageAllocator() override {
    return allocator_->GetPageAllocator();
  }

 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 = GetPageAllocator();
    size_t page_size = page_allocator->AllocatePageSize();
    size_t allocated = RoundUp(length, page_size);
    return i::AllocatePages(page_allocator, allocated, page_size,
                            PageAllocator::kReadWrite);
  }

  void FreeVM(void* data, size_t length) {
    v8::PageAllocator* page_allocator = GetPageAllocator();
    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)
      : limit_(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);
  }

  size_t MaxAllocationSize() const override { return limit_; }

 private:
  size_t limit_;
  std::atomic<size_t> space_left_;
};

#ifdef V8_OS_LINUX

// 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));
    }
#ifdef V8_ENABLE_SANDBOX
    // The backing memory must be allocated inside the sandbox as it will be
    // used for array buffer contents.
    // Here we go into somewhat less-well-defined territory by using the
    // sandbox's virtual address space to essentially just reserve a number of
    // OS pages inside the sandbox, but then using mremap to replace these
    // pages directly afterwards. In practice, this works fine however.
    VirtualAddressSpace* vas = i::Sandbox::current()->address_space();
    i::Address in_sandbox_page_reservation = vas->AllocatePages(
        VirtualAddressSpace::kNoHint, rounded_length,
        vas->allocation_granularity(), PagePermissions::kNoAccess);
    void* virtual_alloc =
        in_sandbox_page_reservation != 0
            ? reinterpret_cast<void*>(in_sandbox_page_reservation)
            : reinterpret_cast<void*>(-1);
#else
    void* virtual_alloc =
        mmap(nullptr, rounded_length, prot, flags | MAP_NORESERVE, -1, 0);
#endif
    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);
#ifdef V8_ENABLE_SANDBOX
          vas->FreePages(in_sandbox_page_reservation, rounded_length);
#else
          munmap(virtual_alloc, rounded_length);
#endif
          return nullptr;
        }
        FATAL("mremap failed with error %d: %s", errno, strerror(errno));
      }
    }
    base::MutexGuard lock_guard(&regions_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(&regions_mutex_);
    void* real_alloc = regions_[data];
    munmap(real_alloc, kChunkSize);
    size_t rounded_length = RoundUp(length, kChunkSize);
#ifdef V8_ENABLE_SANDBOX
    VirtualAddressSpace* vas = i::Sandbox::current()->address_space();
    vas->FreePages(reinterpret_cast<i::Address>(data), rounded_length);
#else
    munmap(data, rounded_length);
#endif
    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  // V8_OS_LINUX

v8::Platform* g_default_platform;
std::unique_ptr<v8::Platform> g_platform;

template <int N>
void ThrowError(Isolate* isolate, const char (&message)[N]) {
  if (isolate->IsExecutionTerminating()) return;
  isolate->ThrowError(message);
}

void ThrowError(Isolate* isolate, std::string_view message) {
  if (isolate->IsExecutionTerminating()) return;
  Local<String> exception =
      String::NewFromUtf8(
          isolate, std::string(message.substr(0, String::kMaxLength)).c_str())
          .ToLocalChecked();
  isolate->ThrowError(exception);
}

void ThrowException(Isolate* isolate, Local<Value> exception) {
  if (isolate->IsExecutionTerminating()) return;
  isolate->ThrowException(exception);
}

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::DirectHandle<i::Object> handle =
      Utils::OpenDirectHandle(*object->GetInternalField(0));
  if (IsSmi(*handle)) {
    ThrowError(isolate, "Worker is defunct because main thread is terminating");
    return nullptr;
  }
  auto managed = i::Cast<i::Managed<Worker>>(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

// static variables:
CounterMap* Shell::counter_map_;
base::Mutex 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::cached_code_mutex_;
std::map<std::string, std::unique_ptr<ScriptCompiler::CachedData>>
    Shell::cached_code_map_;
std::atomic<int> Shell::unhandled_promise_rejections_{0};

#if V8_ENABLE_WEBASSEMBLY
base::LazyMutex Shell::wasm_serialized_bytes_mutex_;
std::unordered_set<size_t> Shell::wasm_serialized_bytes_hashes_;
#endif  // V8_ENABLE_WEBASSEMBLY

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:
  DummySourceStream(Isolate* isolate, Local<String> source) : done_(false) {
    source_length_ = source->Length();
    source_buffer_ = std::make_unique<uint16_t[]>(source_length_);
    source->WriteV2(isolate, 0, source_length_, source_buffer_.get());
  }

  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_ * 2;
  }

 private:
  uint32_t source_length_;
  std::unique_ptr<uint16_t[]> source_buffer_;
  bool done_;
};

// Run a ScriptStreamingTask in a separate thread.
class StreamerThread : public v8::base::Thread {
 public:
  static void StartThreadForTaskAndJoin(
      v8::ScriptCompiler::ScriptStreamingTask* task) {
    StreamerThread thread(task);
    CHECK(thread.Start());
    thread.Join();
  }

  explicit StreamerThread(v8::ScriptCompiler::ScriptStreamingTask* task)
      : Thread(Thread::Options()), task_(task) {}

  void Run() override { task_->Run(); }

 private:
  v8::ScriptCompiler::ScriptStreamingTask* 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(Isolate::GetCurrent(), 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>(isolate, source),
        v8::ScriptCompiler::StreamedSource::TWO_BYTE);
    std::unique_ptr<v8::ScriptCompiler::ScriptStreamingTask> streaming_task(
        v8::ScriptCompiler::StartStreaming(isolate, &streamed_source,
                                           std::is_same_v<T, Module>
                                               ? v8::ScriptType::kModule
                                               : v8::ScriptType::kClassic));
    StreamerThread::StartThreadForTaskAndJoin(streaming_task.get());
    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;

const char kDataURLPrefix[] = "data:text/javascript,";

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:
  static constexpr i::ExternalPointerTag kManagedTag =
      i::kD8ModuleEmbedderDataTag;

  explicit ModuleEmbedderData(Isolate* isolate)
      : isolate_(isolate),
        module_to_specifier_map(10, ModuleGlobalHash(isolate)),
        json_module_to_parsed_json_map(
            10, module_to_specifier_map.hash_function()) {}

  std::string GetModuleSpecifier(Local<Module> module) {
    Global<Module> global_module(isolate_, module);
    auto specifier_it = module_to_specifier_map.find(global_module);
    CHECK(specifier_it != module_to_specifier_map.end());
    return specifier_it->second;
  }

  Local<Module> GetModule(
      std::pair<std::string, ModuleType> module_specifier_and_type) {
    auto module_it = module_map.find(module_specifier_and_type);
    CHECK(module_it != module_map.end());
    return module_it->second.Get(isolate_);
  }

  Local<Object> GetModuleSource(
      std::pair<std::string, ModuleType> module_specifier_and_type) {
    auto module_source_it = module_source_map.find(module_specifier_and_type);
    CHECK(module_source_it != module_source_map.end());
    return module_source_it->second.Get(isolate_);
  }

  Local<Value> GetJsonModuleValue(Local<Module> module) {
    auto json_value_it =
        json_module_to_parsed_json_map.find(Global<Module>(isolate_, module));
    CHECK(json_value_it != json_module_to_parsed_json_map.end());
    return json_value_it->second.Get(isolate_);
  }

  static ModuleType ModuleTypeFromImportSpecifierAndAttributes(
      const std::string& specifier, Local<FixedArray> import_attributes,
      bool hasPositions) {
    Isolate* isolate = Isolate::GetCurrent();
    const int kV8AssertionEntrySize = hasPositions ? 3 : 2;
    for (int i = 0; i < import_attributes->Length();
         i += kV8AssertionEntrySize) {
      Local<String> v8_assertion_key =
          import_attributes->Get(i).As<v8::String>();
      std::string assertion_key = ToSTLString(isolate, v8_assertion_key);

      if (assertion_key == "type") {
        Local<String> v8_assertion_value =
            import_attributes->Get(i + 1).As<String>();
        std::string assertion_value = ToSTLString(isolate, v8_assertion_value);
        if (assertion_value == "json") {
          return ModuleType::kJSON;
        } else {
          // JSON and WebAssembly are currently the only supported non-JS types
          return ModuleType::kInvalid;
        }
      }
    }

    // If no type is asserted, check for the extension. Otherwise default to JS.
    if (specifier.ends_with(".wasm")) {
      return ModuleType::kWebAssembly;
    }
    return ModuleType::kJavaScript;
  }

  Isolate* isolate_;
  // Map from (normalized module specifier, module type) pair to Module.
  std::map<std::pair<std::string, ModuleType>, Global<Module>> module_map;
  // Map from (normalized module specifier, module type) pair to ModuleSource.
  std::map<std::pair<std::string, ModuleType>, Global<Object>>
      module_source_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 = i::Isolate::Current();
  const size_t kModuleEmbedderDataEstimate = 4 * 1024;  // module map.
  i::DirectHandle<i::Managed<ModuleEmbedderData>> module_data_managed =
      i::Managed<ModuleEmbedderData>::From(
          i_isolate, kModuleEmbedderDataEstimate,
          std::make_shared<ModuleEmbedderData>(
              reinterpret_cast<v8::Isolate*>(i_isolate)));
  v8::Local<v8::Data> module_data = Utils::ToLocal(module_data_managed);
  context->SetEmbedderDataV2(kModuleEmbedderDataIndex, module_data);
  return module_data_managed->get();
}

std::shared_ptr<ModuleEmbedderData> GetModuleDataFromContext(
    Local<Context> context) {
  v8::Local<v8::Data> module_data =
      context->GetEmbedderDataV2(kModuleEmbedderDataIndex);
  i::DirectHandle<i::Managed<ModuleEmbedderData>> module_data_managed =
      i::Cast<i::Managed<ModuleEmbedderData>>(
          Utils::OpenDirectHandle<Data, 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(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(0).As<Value>()->Uint32Value(context).To(&magic)) {
    return false;
  }
  if (magic != kHostDefinedOptionsMagicConstant) return false;
  return array->Get(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_);
    v8::Context::Scope context_scope(context);
    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,
                          ReportExceptions report_exceptions,
                          Global<Value>* out_result) {
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  if (i_isolate->is_execution_terminating()) return true;
  if (i::v8_flags.parse_only) {
    i::VMState<PARSER> state(i_isolate);
    i::DirectHandle<i::String> str = Utils::OpenDirectHandle(*(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();
    return false;
  }

  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::DirectHandle<i::Script> i_script(
        i::Cast<i::Script>(
            Utils::OpenDirectHandle(*script)->shared()->script()),
        i_isolate);
    // TODO(cbruni, chromium:1244145): remove once context-allocated.
    i_script->set_host_defined_options(i::Cast<i::FixedArray>(
        *Utils::OpenDirectHandle(*(origin.GetHostDefinedOptions()))));
  }

  MaybeLocal<Value> maybe_result = script->Run(realm);

  if (options.code_cache_options ==
          ShellOptions::CodeCacheOptions::kProduceCacheAfterExecute &&
      !isolate->IsExecutionTerminating()) {
    // 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;
  }
  data->realm_current_ = data->realm_switch_;

  Local<Value> result;
  if (!maybe_result.ToLocal(&result)) {
    if (try_catch.HasTerminated()) {
      // Re-request terminate execution as it's been cleared, so
      // Shell::FinishExecution doesn't waste time draining all enqueued tasks
      // and microtasks.
      isolate->TerminateExecution();
      return true;
    }
    DCHECK(try_catch.HasCaught());
    return false;
  } else if (out_result != nullptr) {
    out_result->Reset(isolate, result);
  }

  // It's possible that a FinalizationRegistry cleanup task threw an error.
  return !try_catch.HasCaught();
}

namespace {

bool IsAbsolutePath(const std::string& path) {
#if defined(V8_OS_WIN)
  // This is an incorrect approximation, but should
  // work for all our test-running cases.
  return path.find(":/") == 1 || path.find(":\\") == 1;
#else
  return path[0] == '/';
#endif
}

std::string GetWorkingDirectory() {
#if defined(V8_OS_WIN)
  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) {
  if (path.starts_with(kDataURLPrefix)) return GetWorkingDirectory();
  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) {
  DCHECK(!path.starts_with(kDataURLPrefix));
  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 defined(V8_OS_WIN)
      if (segments.size() > 1) segments.pop_back();
#else
      if (!segments.empty()) segments.pop_back();
#endif
    } else if (segment != "" && segment != ".") {
      segments.push_back(segment);
    }
  }
  // Join path segments.
  std::ostringstream os;
#if defined(V8_OS_WIN)
  // The drive letter should be present in an absolute path.
  DCHECK(!segments.empty());
  DCHECK_EQ(2UL, segments[0].size());
  DCHECK_EQ(':', segments[0][1]);
  os << segments[0] << "/";
  if (segments.size() > 2) {
    std::copy(segments.begin() + 1, segments.end() - 1,
              std::ostream_iterator<std::string>(os, "/"));
    os << *segments.rbegin();
  } else if (segments.size() == 2) {
    os << segments[1];
  }
#else
  os << "/";
  if (segments.size() > 1) {
    std::copy(segments.begin(), segments.end() - 1,
              std::ostream_iterator<std::string>(os, "/"));
    os << *segments.rbegin();
  } else if (segments.size() == 1) {
    os << segments[0];
  }
#endif
  return os.str();
}

// Resolves specifier to an absolute path if necessary, and does some
// normalization (eliding references to the current directory
// and replacing backslashes with slashes).
//
// If specifier is a data URL or a regular URL, it is returned unchanged.
std::string NormalizeModuleSpecifier(const std::string& specifier,
                                     const std::string& dir_name) {
  if (specifier.starts_with(kDataURLPrefix) ||
      specifier.starts_with("http://") || specifier.starts_with("https://"))
    return specifier;
  return NormalizePath(specifier, dir_name);
}

#if V8_ENABLE_WEBASSEMBLY
std::string RemoveExtension(const std::string& file_path) {
  size_t last_slash = file_path.find_last_of("/\\");
  size_t last_dot = file_path.find_last_of('.');

  // Check that the dot is part of the file name.
  if (last_dot != std::string::npos &&
      (last_slash == std::string::npos || last_dot > last_slash)) {
    return file_path.substr(0, last_dot);
  }
  return file_path;  // No extension to remove
}
#endif  // V8_ENABLE_WEBASSEMBLY

MaybeLocal<Module> ResolveModuleCallback(Local<Context> context,
                                         Local<String> specifier,
                                         Local<FixedArray> import_attributes,
                                         Local<Module> referrer) {
  Isolate* isolate = Isolate::GetCurrent();
  std::shared_ptr<ModuleEmbedderData> module_data =
      GetModuleDataFromContext(context);
  std::string referrer_specifier = module_data->GetModuleSpecifier(referrer);

  std::string stl_specifier = ToSTLString(isolate, specifier);
  std::string absolute_path =
      NormalizeModuleSpecifier(stl_specifier, DirName(referrer_specifier));
  ModuleType module_type =
      ModuleEmbedderData::ModuleTypeFromImportSpecifierAndAttributes(
          stl_specifier, import_attributes, true);
  return module_data->GetModule(std::make_pair(absolute_path, module_type));
}

MaybeLocal<Object> ResolveModuleSourceCallback(
    Local<Context> context, Local<String> specifier,
    Local<FixedArray> import_attributes, Local<Module> referrer) {
  Isolate* isolate = Isolate::GetCurrent();
  std::shared_ptr<ModuleEmbedderData> module_data =
      GetModuleDataFromContext(context);
  std::string referrer_specifier = module_data->GetModuleSpecifier(referrer);

  std::string stl_specifier = ToSTLString(isolate, specifier);
  std::string absolute_path =
      NormalizeModuleSpecifier(stl_specifier, DirName(referrer_specifier));
  ModuleType module_type =
      ModuleEmbedderData::ModuleTypeFromImportSpecifierAndAttributes(
          stl_specifier, import_attributes, true);
  return module_data->GetModuleSource(
      std::make_pair(absolute_path, module_type));
}

}  // anonymous namespace

MaybeLocal<Object> Shell::FetchModuleSource(Local<Module> referrer,
                                            Local<Context> context,
                                            const std::string& module_specifier,
                                            ModuleType module_type) {
  Isolate* isolate = Isolate::GetCurrent();
  std::shared_ptr<ModuleEmbedderData> module_data =
      GetModuleDataFromContext(context);

  // Loading modules is only allowed for local absolute paths.
  if (!IsAbsolutePath(module_specifier)) {
    std::ostringstream msg;
    msg << "d8: Reading module from " << module_specifier
        << " is not supported.";
    if (!referrer.IsEmpty()) {
      std::string referrer_specifier =
          module_data->GetModuleSpecifier(referrer);
      msg << "\n    imported by " << referrer_specifier;
    }
    ThrowError(isolate, msg.view());
    return MaybeLocal<Object>();
  }

  auto file = ReadFileData(isolate, module_specifier.c_str());
  if (!file) {
    std::ostringstream msg;
    msg << "d8: Error reading module from " << module_specifier;
    if (!referrer.IsEmpty()) {
      std::string referrer_specifier =
          module_data->GetModuleSpecifier(referrer);
      msg << "\n    imported by " + referrer_specifier;
    }
    ThrowError(isolate, msg.view());
    return MaybeLocal<Object>();
  }

  Local<Object> module_source;
  switch (module_type) {
    case ModuleType::kWebAssembly: {
      if (!v8::WasmModuleObject::Compile(
               isolate,
               MemorySpan<const uint8_t>(static_cast<uint8_t*>(file->memory()),
                                         file->size()))
               .ToLocal(&module_source)) {
        return MaybeLocal<Object>();
      }
      break;
    }
    default:
      // https://tc39.es/proposal-source-phase-imports/#table-abstract-methods-of-module-records
      // For Module Records that do not have a source representation,
      // GetModuleSource() must always return a throw completion whose [[Value]]
      // is a ReferenceError.
      ThrowException(
          isolate, v8::Exception::SyntaxError(String::NewFromUtf8Literal(
                       isolate, "Module source can not be imported for type")));
      return MaybeLocal<Object>();
  }

  CHECK(
      module_data->module_source_map
          .insert(std::make_pair(std::make_pair(module_specifier, module_type),
                                 Global<Object>(isolate, module_source)))
          .second);
  return module_source;
}

// file_name must be either an absolute path to the filesystem or a data URL.
MaybeLocal<Module> Shell::FetchModuleTree(Local<Module> referrer,
                                          Local<Context> context,
                                          const std::string& module_specifier,
                                          ModuleType module_type) {
  Isolate* isolate = Isolate::GetCurrent();
  std::shared_ptr<ModuleEmbedderData> module_data =
      GetModuleDataFromContext(context);
  MaybeLocal<String> source_text;
  if (module_specifier.starts_with(kDataURLPrefix)) {
    source_text = String::NewFromUtf8(
        isolate, module_specifier.c_str() + strlen(kDataURLPrefix));
  } else if (IsAbsolutePath(module_specifier)) {
    source_text = ReadFile(isolate, module_specifier.c_str(), false);
    if (source_text.IsEmpty() && options.fuzzy_module_file_extensions) {
      std::string fallback_file_name = module_specifier + ".js";
      source_text = ReadFile(isolate, fallback_file_name.c_str(), false);
      if (source_text.IsEmpty()) {
        fallback_file_name = module_specifier + ".mjs";
        source_text = ReadFile(isolate, fallback_file_name.c_str());
      }
    }
  } else {
    // Loading modules is only allowed for local absolute paths.
    std::ostringstream msg;
    msg << "d8: Reading module from " << module_specifier
        << " is not supported.";
    if (!referrer.IsEmpty()) {
      std::string referrer_specifier =
          module_data->GetModuleSpecifier(referrer);
      msg << "\n    imported by " << referrer_specifier;
    }
    ThrowError(isolate, msg.view());
    return MaybeLocal<Module>();
  }

  if (source_text.IsEmpty()) {
    std::ostringstream msg;
    msg << "d8: Error reading module from " << module_specifier;
    if (!referrer.IsEmpty()) {
      std::string referrer_specifier =
          module_data->GetModuleSpecifier(referrer);
      msg << "\n    imported by " + referrer_specifier;
    }
    ThrowError(isolate, msg.view());
    return MaybeLocal<Module>();
  }

  Local<String> resource_name =
      String::NewFromUtf8(isolate, module_specifier.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>();
    }

    auto export_names = v8::to_array<Local<String>>(
        {String::NewFromUtf8(isolate, "default").ToLocalChecked()});

    module = v8::Module::CreateSyntheticModule(
        isolate,
        String::NewFromUtf8(isolate, module_specifier.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();
  }

  auto module_map_inserted = module_data->module_map.insert(
      std::make_pair(std::make_pair(module_specifier, module_type),
                     Global<Module>(isolate, module)));
  CHECK(module_map_inserted.second);

  auto module_to_specifier_map_inserted =
      module_data->module_to_specifier_map.insert(
          std::make_pair(Global<Module>(isolate, module), module_specifier));
  CHECK(module_to_specifier_map_inserted.second);

  std::string dir_name = DirName(module_specifier);

  MaybeLocal<Module> result(module);

  Local<FixedArray> module_requests = module->GetModuleRequests();
  for (int i = 0, length = module_requests->Length(); i < length; ++i) {
    Local<ModuleRequest> module_request =
        module_requests->Get(i).As<ModuleRequest>();
    std::string specifier =
        ToSTLString(isolate, module_request->GetSpecifier());
    std::string normalized_specifier =
        NormalizeModuleSpecifier(specifier, dir_name);
    Local<FixedArray> import_attributes = module_request->GetImportAttributes();
    ModuleType request_module_type =
        ModuleEmbedderData::ModuleTypeFromImportSpecifierAndAttributes(
            normalized_specifier, import_attributes, true);

    if (request_module_type == ModuleType::kInvalid) {
      ThrowError(isolate, "Invalid module type was asserted");
      result = {};
      break;
    }

    if (module_request->GetPhase() == ModuleImportPhase::kSource) {
      if (module_data->module_source_map.count(
              std::make_pair(normalized_specifier, request_module_type))) {
        continue;
      }

      if (FetchModuleSource(module, context, normalized_specifier,
                            request_module_type)
              .IsEmpty()) {
        result = {};
        break;
      }
    } else {
      if (module_data->module_map.count(
              std::make_pair(normalized_specifier, request_module_type))) {
        continue;
      }

      if (FetchModuleTree(module, context, normalized_specifier,
                          request_module_type)
              .IsEmpty()) {
        result = {};
        break;
      }
    }
  }

  if (result.IsEmpty()) {
    module_data->module_map.erase(module_map_inserted.first);
    module_data->module_to_specifier_map.erase(
        module_to_specifier_map_inserted.first);
  }

  return result;
}

MaybeLocal<Value> Shell::JSONModuleEvaluationSteps(Local<Context> context,
                                                   Local<Module> module) {
  Isolate* isolate = Isolate::GetCurrent();

  std::shared_ptr<ModuleEmbedderData> module_data =
      GetModuleDataFromContext(context);
  Local<Value> json_value = module_data->GetJsonModuleValue(module);

  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_,
                    ModuleImportPhase phase_,
                    Local<FixedArray> import_attributes_,
                    Local<Promise::Resolver> resolver_)
      : isolate(isolate_), phase(phase_) {
    context.Reset(isolate, context_);
    referrer.Reset(isolate, referrer_);
    specifier.Reset(isolate, specifier_);
    import_attributes.Reset(isolate, import_attributes_);
    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;
  ModuleImportPhase phase;
  Global<FixedArray> import_attributes;
  Global<Promise::Resolver> resolver;
};

namespace {

enum ModuleResolutionDataIndex : uint32_t {
  kResolver = 0,
  kNamespaceOrSource = 1,
};

}  // namespace

void Shell::ModuleResolutionSuccessCallback(
    const FunctionCallbackInfo<Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate(info.GetIsolate());
  HandleScope handle_scope(isolate);
  Local<Array> module_resolution_data(info.Data().As<Array>());
  Local<Context> context(isolate->GetCurrentContext());

  Local<Promise::Resolver> resolver(
      module_resolution_data->Get(context, ModuleResolutionDataIndex::kResolver)
          .ToLocalChecked()
          .As<Promise::Resolver>());
  Local<Value> namespace_or_source(
      module_resolution_data
          ->Get(context, ModuleResolutionDataIndex::kNamespaceOrSource)
          .ToLocalChecked());

  PerIsolateData* data = PerIsolateData::Get(isolate);
  Local<Context> realm = data->realms_[data->realm_current_].Get(isolate);
  Context::Scope context_scope(realm);

  resolver->Resolve(realm, namespace_or_source).ToChecked();
}

void Shell::ModuleResolutionFailureCallback(
    const FunctionCallbackInfo<Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate(info.GetIsolate());
  HandleScope handle_scope(isolate);
  Local<Array> module_resolution_data(info.Data().As<Array>());
  Local<Context> context(isolate->GetCurrentContext());

  Local<Promise::Resolver> resolver(
      module_resolution_data->Get(context, ModuleResolutionDataIndex::kResolver)
          .ToLocalChecked()
          .As<Promise::Resolver>());

  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_attributes) {
  return HostImportModuleWithPhaseDynamically(
      context, host_defined_options, resource_name, specifier,
      ModuleImportPhase::kEvaluation, import_attributes);
}

MaybeLocal<Promise> Shell::HostImportModuleWithPhaseDynamically(
    Local<Context> context, Local<Data> host_defined_options,
    Local<Value> resource_name, Local<String> specifier,
    ModuleImportPhase phase, Local<FixedArray> import_attributes) {
  Isolate* isolate = Isolate::GetCurrent();

  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, phase,
                              import_attributes, 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 = Isolate::GetCurrent();
  HandleScope handle_scope(isolate);

  std::shared_ptr<ModuleEmbedderData> module_data =
      GetModuleDataFromContext(context);
  std::string specifier = module_data->GetModuleSpecifier(module);

  Local<String> url_key =
      String::NewFromUtf8Literal(isolate, "url", NewStringType::kInternalized);
  Local<String> url =
      String::NewFromUtf8(isolate, specifier.c_str()).ToLocalChecked();
  meta->CreateDataProperty(context, url_key, url).ToChecked();
}

MaybeLocal<Context> Shell::HostCreateShadowRealmContext(
    Local<Context> initiator_context) {
  Local<Context> context = v8::Context::New(Isolate::GetCurrent());
  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;
}

namespace {
void RejectPromiseIfExecutionIsNotTerminating(Isolate* isolate,
                                              Local<Context> realm,
                                              Local<Promise::Resolver> resolver,
                                              const TryCatch& try_catch) {
  CHECK(try_catch.HasCaught());
  if (isolate->IsExecutionTerminating()) {
    Shell::ReportException(isolate, try_catch);
  } else {
    resolver->Reject(realm, try_catch.Exception()).ToChecked();
  }
}

Maybe<bool> ChainDynamicImportPromise(Isolate* isolate, Local<Context> realm,
                                      v8::Local<v8::Promise::Resolver> resolver,
                                      Local<Promise> result_promise,
                                      Local<Value> namespace_or_source) {
  Local<Array> module_resolution_data = v8::Array::New(isolate);
  if (module_resolution_data->SetPrototypeV2(realm, v8::Null(isolate))
          .IsNothing()) {
    return Nothing<bool>();
  }
  if (module_resolution_data
          ->Set(realm, ModuleResolutionDataIndex::kResolver, resolver)
          .IsNothing()) {
    return Nothing<bool>();
  }
  if (module_resolution_data
          ->Set(realm, ModuleResolutionDataIndex::kNamespaceOrSource,
                namespace_or_source)
          .IsNothing()) {
    return Nothing<bool>();
  }
  Local<Function> callback_success;
  if (!Function::New(realm, Shell::ModuleResolutionSuccessCallback,
                     module_resolution_data)
           .ToLocal(&callback_success)) {
    return Nothing<bool>();
  }

  Local<Function> callback_failure;
  if (!Function::New(realm, Shell::ModuleResolutionFailureCallback,
                     module_resolution_data)
           .ToLocal(&callback_failure)) {
    return Nothing<bool>();
  }
  if (result_promise->Then(realm, callback_success, callback_failure)
          .IsEmpty()) {
    return Nothing<bool>();
  }
  return Just(true);
}
}  // namespace

void Shell::DoHostImportModuleDynamically(void* data) {
  Isolate* current_isolate = reinterpret_cast<Isolate*>(i::Isolate::Current());
  DynamicImportData* import_data =
      PerIsolateData::Get(current_isolate)->LookupImportData(data);
  CHECK_EQ(current_isolate, import_data->isolate);

  Isolate* isolate(import_data->isolate);
  Global<Context> global_realm;
  Global<Promise::Resolver> global_resolver;
  Global<Promise> global_result_promise;
  Global<Value> global_namespace_or_source;

  TryCatch try_catch(isolate);
  try_catch.SetVerbose(true);

  {
    HandleScope handle_scope(isolate);
    Local<Context> realm = import_data->context.Get(isolate);
    Local<Value> referrer = import_data->referrer.Get(isolate);
    Local<String> v8_specifier = import_data->specifier.Get(isolate);
    ModuleImportPhase phase = import_data->phase;
    Local<FixedArray> import_attributes =
        import_data->import_attributes.Get(isolate);
    Local<Promise::Resolver> resolver = import_data->resolver.Get(isolate);

    global_realm.Reset(isolate, realm);
    global_resolver.Reset(isolate, resolver);

    PerIsolateData::Get(isolate)->DeleteDynamicImportData(import_data);

    Context::Scope context_scope(realm);
    std::string specifier = ToSTLString(isolate, v8_specifier);

    ModuleType module_type =
        ModuleEmbedderData::ModuleTypeFromImportSpecifierAndAttributes(
            specifier, import_attributes, false);

    if (module_type == ModuleType::kInvalid) {
      ThrowError(isolate, "Invalid module type was asserted");
      RejectPromiseIfExecutionIsNotTerminating(isolate, realm, resolver,
                                               try_catch);
      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(NormalizeModuleSpecifier(source_url, GetWorkingDirectory()));
    std::string absolute_path = NormalizeModuleSpecifier(specifier, dir_name);

    switch (phase) {
      case ModuleImportPhase::kSource: {
        Local<Object> module_source;
        auto module_it = module_data->module_source_map.find(
            std::make_pair(absolute_path, module_type));
        if (module_it != module_data->module_source_map.end()) {
          module_source = module_it->second.Get(isolate);
        } else if (!FetchModuleSource(Local<Module>(), realm, absolute_path,
                                      module_type)
                        .ToLocal(&module_source)) {
          break;
        }
        Local<Promise::Resolver> module_resolver =
            Promise::Resolver::New(realm).ToLocalChecked();
        module_resolver->Resolve(realm, module_source).ToChecked();

        global_namespace_or_source.Reset(isolate, module_source);
        global_result_promise.Reset(isolate, module_resolver->GetPromise());
        break;
      }
      case v8::ModuleImportPhase::kEvaluation: {
        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)) {
          break;
        }
        if (root_module
                ->InstantiateModule(realm, ResolveModuleCallback,
                                    ResolveModuleSourceCallback)
                .FromMaybe(false)) {
          MaybeLocal<Value> maybe_result = root_module->Evaluate(realm);
          if (maybe_result.IsEmpty()) break;
          global_result_promise.Reset(
              isolate, maybe_result.ToLocalChecked().As<Promise>());
          global_namespace_or_source.Reset(isolate,
                                           root_module->GetModuleNamespace());
        }
        break;
      }
      default: {
        UNREACHABLE();
      }
    }
  }

  if (global_result_promise.IsEmpty()) {
    HandleScope handle_scope(isolate);
    Local<Context> realm = global_realm.Get(isolate);
    Local<Promise::Resolver> resolver = global_resolver.Get(isolate);
    RejectPromiseIfExecutionIsNotTerminating(isolate, realm, resolver,
                                             try_catch);
    return;
  }

  {
    // This method is invoked from a microtask, where in general we may have
    // a non-trivial stack. Emptying the message queue below may trigger the
    // execution of a stackless GC. We need to override the embedder stack
    // state, to force scanning the stack, if this happens.
    i::Heap* heap = reinterpret_cast<i::Isolate*>(isolate)->heap();
    i::EmbedderStackStateScope scope(
        heap, i::EmbedderStackStateOrigin::kExplicitInvocation,
        StackState::kMayContainHeapPointers);
    EmptyMessageQueues(isolate);
  }

  // Setup callbacks, and then chain them to the result promise.
  HandleScope handle_scope(isolate);
  Local<Context> realm = global_realm.Get(isolate);
  Local<Promise::Resolver> resolver = global_resolver.Get(isolate);
  Local<Promise> result_promise = global_result_promise.Get(isolate);
  Local<Value> namespace_or_source = global_namespace_or_source.Get(isolate);

  Context::Scope context_scope(realm);

  // Chaining the promise generally does not throw, but execution may be
  // terminating (e.g. when within a worker being terminated). Check the return
  // value to display a helpful message.
  if (ChainDynamicImportPromise(isolate, realm, resolver, result_promise,
                                namespace_or_source)
          .IsNothing()) {
    RejectPromiseIfExecutionIsNotTerminating(isolate, realm, resolver,
                                             try_catch);
  }
}

bool Shell::ExecuteModule(Isolate* isolate, const char* file_name) {
  HandleScope handle_scope(isolate);
  Global<Module> global_root_module;
  Global<Promise> global_result_promise;

  // 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);

  {
    PerIsolateData* data = PerIsolateData::Get(isolate);
    Local<Context> realm = data->realms_[data->realm_current_].Get(isolate);
    Context::Scope context_scope(realm);

    std::string absolute_path =
        NormalizeModuleSpecifier(file_name, GetWorkingDirectory());

    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;
    }
    global_root_module.Reset(isolate, root_module);

    module_data->origin = absolute_path;

    MaybeLocal<Value> maybe_result;
    if (root_module
            ->InstantiateModule(realm, ResolveModuleCallback,
                                ResolveModuleSourceCallback)
            .FromMaybe(false)) {
      maybe_result = root_module->Evaluate(realm);
      CHECK(!maybe_result.IsEmpty());
      global_result_promise.Reset(isolate,
                                  maybe_result.ToLocalChecked().As<Promise>());
    }
  }

  if (!global_result_promise.IsEmpty()) {
    EmptyMessageQueues(isolate);
  } else {
    DCHECK(try_catch.HasCaught());
    ReportException(isolate, try_catch);
    return false;
  }

  // Loop until module execution finishes
  while (isolate->HasPendingBackgroundTasks() ||
         (i::ValueHelper::HandleAsValue(global_result_promise)->State() ==
              Promise::kPending &&
          reinterpret_cast<i::Isolate*>(isolate)
                  ->default_microtask_queue()
                  ->size() > 0)) {
    Shell::CompleteMessageLoop(isolate);
  }

  {
    Local<Promise> result_promise = global_result_promise.Get(isolate);
    Local<Module> root_module = global_root_module.Get(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;
    }

    auto [stalled_modules, stalled_messages] =
        root_module->GetStalledTopLevelAwaitMessages(isolate);
    DCHECK_EQ(stalled_modules.size(), stalled_messages.size());
    if (stalled_messages.size() > 0) {
      Local<Message> message = stalled_messages[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
  }
  for (DynamicImportData* data : import_data_) {
    delete data;
  }
}

void PerIsolateData::RemoveUnhandledPromise(Local<Promise> promise) {
  if (ignore_unhandled_promises_) return;
  // Remove handled promises from the list
  DCHECK_EQ(Isolate::GetCurrent(), 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(Isolate::GetCurrent(), 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) {
  SBXCHECK_EQ(import_data_.end(), import_data_.find(data));
  import_data_.insert(data);
}
void PerIsolateData::DeleteDynamicImportData(DynamicImportData* data) {
  SBXCHECK_NE(import_data_.end(), import_data_.find(data));
  import_data_.erase(data);
  delete data;
}
DynamicImportData* PerIsolateData::LookupImportData(void* data) {
  auto* result = static_cast<DynamicImportData*>(data);
  SBXCHECK_NE(import_data_.end(), import_data_.find(result));
  return result;
}

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);
}

bool PerIsolateData::HasRunningSubscribedWorkers() {
  // Only consider subscribed workers, so that code that spawns a worker and
  // never subscribes to message events will quit.
  return !worker_message_callbacks_.empty();
}

void PerIsolateData::RegisterWorker(std::shared_ptr<Worker> worker) {
  registered_workers_.insert(std::move(worker));
}

void PerIsolateData::SubscribeWorkerOnMessage(
    const std::shared_ptr<Worker>& worker, Local<Context> context,
    Local<Function> callback) {
  if (!registered_workers_.contains(worker)) {
    // The worker has already terminated, so it won't be posting any more
    // messages. Don't try to subscribe to its events.
    fprintf(
        stderr,
        "Trying to subscribe to message events from a terminated worker -- "
        "consider registering the event handler before the event loop runs.\n");
    return;
  }
  worker_message_callbacks_.emplace(
      worker, std::make_pair(Global<Context>(isolate_, context),
                             Global<Function>(isolate_, callback)));
}

std::pair<Local<Context>, Local<Function>> PerIsolateData::GetWorkerOnMessage(
    const std::shared_ptr<Worker>& worker) const {
  auto it = worker_message_callbacks_.find(worker);
  if (it == worker_message_callbacks_.end()) {
    return {};
  }
  return {it->second.first.Get(isolate_), it->second.second.Get(isolate_)};
}

void PerIsolateData::UnregisterWorker(const std::shared_ptr<Worker>& worker) {
  registered_workers_.erase(worker);
  worker_message_callbacks_.erase(worker);
}

PerIsolateData::RealmScope::RealmScope(Isolate* isolate,
                                       const Global<Context>& context)
    : data_(PerIsolateData::Get(isolate)) {
  data_->realm_count_ = 1;
  data_->realm_current_ = 0;
  data_->realm_switch_ = 0;
  data_->realms_ = new Global<Context>[1];
  data_->realms_[0].Reset(data_->isolate_, context);
}

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) {
  Isolate* isolate = info.GetIsolate();
  if (info.Length() < arg_offset || !info[arg_offset]->IsNumber()) {
    ThrowError(isolate, "Invalid argument");
    return -1;
  }
  int index =
      info[arg_offset]->Int32Value(isolate->GetCurrentContext()).FromMaybe(-1);
  if (index < 0 || index >= realm_count_ || realms_[index].IsEmpty()) {
    ThrowError(isolate, "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;
}

#ifdef V8_OS_LINUX
void SendPerfControlCommand(const char* command) {
  if (Shell::options.perf_ctl_fd != -1 && Shell::options.perf_ack_fd != -1) {
    size_t command_len = strlen(command);
    ssize_t ret = write(Shell::options.perf_ctl_fd, command, command_len);
    if (ret == -1) {
      fprintf(stderr, "perf_ctl write error: %s\n", strerror(errno));
    }
    CHECK_EQ(ret, command_len);

    char ack[5];
    ret = read(Shell::options.perf_ack_fd, ack, 5);
    if (ret == -1) {
      fprintf(stderr, "perf_ack read error: %s\n", strerror(errno));
    }
    CHECK_EQ(ret, 5);
    CHECK_EQ(strcmp(ack, "ack\n"), 0);
  }
}
#endif

// 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();
  PerIsolateData* data = PerIsolateData::Get(isolate);
  Local<Context> context = isolate->GetCurrentContext();

  if (info.Length() < 1 || !info[0]->IsString()) {
    ThrowError(isolate, "Invalid 'name' argument");
    return;
  }
  Local<String> name = info[0].As<String>();
  double timestamp = GetTimestamp();

  {
    String::Utf8Value utf8_name(isolate, name);
    data->performance_mark_map_[*utf8_name] = timestamp;
  }

  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);

#ifdef V8_OS_LINUX
  if (options.scope_linux_perf_to_mark_measure) {
    SendPerfControlCommand("enable");
  }
#endif
}

bool Shell::LookupPerformanceMark(Isolate* isolate, v8::Local<String> name,
                                  double* result) {
  String::Utf8Value utf8_name(isolate, name);
  PerIsolateData* data = PerIsolateData::Get(isolate);
  if ((data->performance_mark_map_).find(*utf8_name) !=
      (data->performance_mark_map_).end()) {
    *result = (data->performance_mark_map_)[*utf8_name];
    return true;
  }
  std::ostringstream msg;
  msg << "Invalid performance.mark \"";
  msg << (*utf8_name) << "\" does not exist";
  ThrowError(isolate, msg.str());
  return false;
}

// 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(isolate, "Invalid 'name' argument");
    return;
  }

  v8::Local<String> name = info[0].As<String>();
  double start_timestamp = 0;
  double end_timestamp = GetTimestamp();

#ifdef V8_OS_LINUX
  if (options.scope_linux_perf_to_mark_measure) {
    SendPerfControlCommand("disable");
  }
#endif

  Local<Value> start_mark = info[1].As<Value>();
  if (start_mark->IsString()) {
    if (!LookupPerformanceMark(isolate, start_mark.As<String>(),
                               &start_timestamp)) {
      return;
    }
    if (info.Length() == 3) {
      Local<Value> end_mark = info[2].As<Value>();
      if (!end_mark->IsString()) {
        ThrowError(isolate, "Expect string as end mark.");
        return;
      } else if (!LookupPerformanceMark(isolate, end_mark.As<String>(),
                                        &end_timestamp)) {
        return;
      }
    }
  } else if (start_mark->IsUndefined()) {
    // Just use the default start_timestamp = 0;
  } else if (!start_mark->IsObject()) {
    ThrowError(isolate, "Invalid 'startMark' argument: Not an Object");
    return;
  } else if (info.Length() > 2) {
    ThrowError(isolate, "Too many arguments");
    return;
  } else {
    // Legacy d8 behavior, not spec-compliant, because we expect a
    // PerformanceMark-like object here, not the measureOptions argument
    // as per the spec.
    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(isolate,
                 "Invalid 'startMark' argument: No numeric 'startTime' field");
      return;
    }
    start_timestamp = start_time_field.As<Number>()->Value();
  }

  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();
  isolate->MeasureMemory(v8::MeasureMemoryDelegate::Default(
                             isolate, context, promise_resolver, mode),
                         v8::MeasureMemoryExecution::kEager);
  info.GetReturnValue().Set(promise_resolver->GetPromise());
}

// Stub for differential fuzzing.
void Shell::PerformanceStub(const v8::FunctionCallbackInfo<v8::Value>& info) {
  info.GetReturnValue().Set(v8::Undefined(info.GetIsolate()));
}

// 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(isolate, "Invalid argument");
    return;
  }
  Local<Object> object =
      info[0]->ToObject(isolate->GetCurrentContext()).ToLocalChecked();
  i::DirectHandle<i::JSReceiver> i_object = Utils::OpenDirectHandle(*object);
  if (IsJSGlobalProxy(*i_object) &&
      i::Cast<i::JSGlobalProxy>(i_object)->IsDetached()) {
    return;
  }
  Local<Context> creation_context;
  if (!object->GetCreationContext(isolate).ToLocal(&creation_context)) {
    ThrowError(isolate, "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));
  Isolate* isolate = info.GetIsolate();
  PerIsolateData* data = PerIsolateData::Get(isolate);
  int index = data->RealmIndexOrThrow(info, 0);
  if (index == -1) return;
  Local<Object> global =
      Local<Context>::New(isolate, data->realms_[index])->Global();
  // Sanity check that v8::Context::Global() returned global proxy.
  CHECK(IsJSGlobalProxy(*Utils::OpenDirectHandle(*global)));
  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();
  if (isolate->IsExecutionTerminating()) return MaybeLocal<Context>();

  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(v8::ContextDependants::kSomeDependants);
}

// 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));

  // Explicitly check for stack overflows. This method can call into JS
  // code via extensions, and will consume significant stack space before that,
  // so that stack check might come too late.
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(info.GetIsolate());
  if (i::StackLimitCheck{i_isolate}.HasOverflowed()) {
    i_isolate->StackOverflow();
    return;
  }

  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));

  // Explicitly check for stack overflows. This method can call into JS
  // code via extensions, and will consume significant stack space before that,
  // so that stack check might come too late.
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(info.GetIsolate());
  if (i::StackLimitCheck{i_isolate}.HasOverflowed()) {
    i_isolate->StackOverflow();
    return;
  }

  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. The old context is disposed.
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(isolate, "Invalid realm index");
    return;
  }

  Local<Context> context = Local<Context>::New(isolate, data->realms_[index]);
  v8::Local<Value> global = context->Global();
  CHECK(!global.IsEmpty());

  DisposeRealm(info, index);
  CreateRealm(info, index, global);
}

// Realm.navigateSameOrigin(i) creates a new realm with the same security token
// in place of realm i. The old realm's context is left around in detached
// state.
void Shell::RealmNavigateSameOrigin(
    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(isolate, "Invalid realm index");
    return;
  }

  Local<Context> context = Local<Context>::New(isolate, data->realms_[index]);
  v8::Local<Value> global = context->Global();
  context->DetachGlobal();
  CHECK(!global.IsEmpty());

  Local<Value> old_security_token = context->GetSecurityToken();
  Local<Context> new_context;
  if (CreateRealm(info, index, global).ToLocal(&new_context)) {
    new_context->SetSecurityToken(old_security_token);
  }
}

// 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(isolate, "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(isolate, "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);

  if (isolate->IsExecutionTerminating()) return;
  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<Name> 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<Name> 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 (!IsHeapObject(*arg_handle) ||
      !IsJSFunctionOrBoundFunctionOrWrappedFunction(
          *i::Cast<i::HeapObject>(arg_handle))) {
    ThrowError(isolate, "Expected function as single argument.");
    return;
  }

  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  HandleScope handle_scope(isolate);

  auto callable =
      i::Cast<i::JSFunctionOrBoundFunctionOrWrappedFunction>(arg_handle);
  while (IsJSBoundFunction(*callable)) {
    internal::DisallowGarbageCollection no_gc;
    auto bound_function = i::Cast<i::JSBoundFunction>(callable);
    auto bound_target = bound_function->bound_target_function();
    if (!IsJSFunctionOrBoundFunctionOrWrappedFunction(bound_target)) {
      internal::AllowGarbageCollection allow_gc;
      ThrowError(isolate, "Expected function as bound target.");
      return;
    }
    callable = handle(
        i::Cast<i::JSFunctionOrBoundFunctionOrWrappedFunction>(bound_target),
        i_isolate);
  }

  i::DirectHandle<i::JSFunction> function = i::Cast<i::JSFunction>(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::TrustedByteArray> bytecode_offsets;
  std::unique_ptr<i::baseline::BytecodeOffsetIterator> offset_iterator;
  if (has_baseline) {
    bytecode_offsets =
        handle(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());
}

void Shell::SetFlushDenormals(const v8::FunctionCallbackInfo<v8::Value>& info) {
  Isolate* isolate = info.GetIsolate();
  if (i::v8_flags.correctness_fuzzer_suppressions || i::v8_flags.fuzzing) {
    // Setting denormals flushing in the middle of code is almost certain to
    // cause correctness issues or crashes, in a way that isn't interesting to
    // us. Make this a no-op instead.
    return;
  }
  // Check if the argument is a valid function.
  if (info.Length() != 1) {
    ThrowError(isolate, "Expected single boolean argument.");
    return;
  }
  Local<Value> arg = info[0];
  if (!arg->IsBoolean()) {
    ThrowError(isolate, "Expected single boolean argument.");
    return;
  }
  base::FPU::SetFlushDenormals(arg.As<Boolean>()->BooleanValue(isolate));
}

// 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 hooks 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);
}

#if V8_ENABLE_WEBASSEMBLY

void Shell::WasmSerializeModule(
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  Isolate* isolate = info.GetIsolate();
  HandleScope handle_scope(isolate);
  if (!info[0]->IsWasmModuleObject()) {
    ThrowError(isolate, "First argument must be a WasmModuleObject");
    return;
  }

  i::DirectHandle<i::WasmModuleObject> module_obj =
      i::Cast<i::WasmModuleObject>(Utils::OpenHandle(*info[0]));

  i::wasm::NativeModule* native_module = module_obj->native_module();
  DCHECK(!native_module->compilation_state()->failed());

  i::wasm::WasmSerializer wasm_serializer(native_module);

  // The content of the serialized byte buffer is nondeterministic (depends
  // e.g. on timing, but also on the platform and on enabled features).
  // Thus, return an empty array for correctness fuzzing.
  size_t byte_length = i::v8_flags.correctness_fuzzer_suppressions
                           ? 0
                           : wasm_serializer.GetSerializedNativeModuleSize();

  Local<ArrayBuffer> array_buffer = ArrayBuffer::New(isolate, byte_length);

  base::Vector<uint8_t> byte_buffer{
      static_cast<uint8_t*>(array_buffer->GetBackingStore()->Data()),
      byte_length};
  if (!i::v8_flags.correctness_fuzzer_suppressions &&
      !wasm_serializer.SerializeNativeModule(byte_buffer)) {
    CHECK(i::v8_flags.fuzzing);
    return;
  }

  // Remember a hash of the serialized bytes. This will be used to check that
  // only serialized bytes are accepted when deserializing.
  {
    size_t hash = base::Hasher{}
                      .AddRange(native_module->wire_bytes())
                      .AddRange(byte_buffer)
                      .hash();
    base::MutexGuard mutex_guard(Shell::wasm_serialized_bytes_mutex_.Pointer());
    Shell::wasm_serialized_bytes_hashes_.insert(hash);
  }

  info.GetReturnValue().Set(array_buffer);
}

void Shell::WasmDeserializeModule(
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  Isolate* isolate = info.GetIsolate();
  static std::atomic<bool> print_warning{true};
  if (print_warning.exchange(false, std::memory_order_relaxed)) {
    i::PrintF(
        "NOTE: d8.wasm.deserializeModule() is not safe against manipulated "
        "input. It's not VRP eligible.\n");
  }
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  HandleScope handle_scope(isolate);
  if (!info[0]->IsArrayBuffer()) {
    ThrowError(isolate, "First argument must be an ArrayBuffer");
    return;
  }
  if (!info[1]->IsTypedArray()) {
    ThrowError(isolate, "Second argument must be a TypedArray");
    return;
  }

  i::wasm::WasmEnabledFeatures enabled_features =
      i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate);
  i::wasm::CompileTimeImports compile_time_imports =
      i::WasmJs::CompileTimeImportsFromArgument(
          Utils::OpenDirectHandle(*info[2]), i_isolate, enabled_features);

  Local<ArrayBuffer> serialized_bytes_buffer = info[0].As<ArrayBuffer>();
  Local<ArrayBufferView> wire_bytes_view = info[1].As<ArrayBufferView>();

  // Note: These checks must be executed *after* evaluating compile time
  // imports, as that calls back into JS and can detach buffers.
  if (serialized_bytes_buffer->WasDetached()) {
    ThrowError(isolate, "First argument is detached");
    return;
  }
  if (wire_bytes_view->Buffer()->WasDetached()) {
    ThrowError(isolate, "Second argument's buffer is detached");
    return;
  }

  // Make copies of the provided buffers, to avoid manipulation during
  // deserialization.
  // For the wire bytes, we need a new copy anyway for storing in the new
  // NativeModule (if it does not come from the cache).
  base::OwnedVector<const uint8_t> wire_bytes_vec = ([&] {
    size_t length = wire_bytes_view->ByteLength();
    auto vec = base::OwnedVector<uint8_t>::NewForOverwrite(length);
    CHECK_EQ(length, wire_bytes_view->CopyContents(vec.data(), length));
    return vec;  // `OwnedVector<uint8_t>` to `OwnedVector<const uint8_t>`.
  })();
  base::OwnedVector<uint8_t> serialized_bytes_vec = base::OwnedCopyOf(
      reinterpret_cast<uint8_t*>(serialized_bytes_buffer->Data()),
      serialized_bytes_buffer->ByteLength());

  // Check that we only try to deserialize bytes that we previously produced via
  // serialization.
  // This is prone to hash collisions, but this check sufficiently prevents
  // fuzzers from passing manipulated bytes (or bytes that do not match the wire
  // bytes) in regular fuzzing.
  {
    size_t hash = base::Hasher{}
                      .AddRange(wire_bytes_vec.as_vector())
                      .AddRange(serialized_bytes_vec.as_vector())
                      .hash();
    base::MutexGuard mutex_guard(Shell::wasm_serialized_bytes_mutex_.Pointer());
    if (!Shell::wasm_serialized_bytes_hashes_.count(hash)) {
      ThrowError(isolate, "Trying to deserialize manipulated bytes");
      return;
    }
  }

  i::DirectHandle<i::WasmModuleObject> module_object;

  // For correctness fuzzing, the `serializeModule` function always returns an
  // empty buffer. We thus have to accept this empty buffer again here, and
  // produce a valid module.
  if (i::v8_flags.correctness_fuzzer_suppressions) {
    CHECK(serialized_bytes_vec.empty());
    i::wasm::ErrorThrower thrower{i_isolate, "d8.wasm.deserializeModule"};
    module_object =
        i::wasm::GetWasmEngine()
            ->SyncCompile(i_isolate, enabled_features, compile_time_imports,
                          &thrower, std::move(wire_bytes_vec))
            .ToHandleChecked();
    DCHECK(!thrower.error());
  } else {
    // Note that {wasm::DeserializeNativeModule} will allocate. We assume the
    // JSArrayBuffer backing store doesn't get relocated.
    if (!i::wasm::DeserializeNativeModule(
             i_isolate, enabled_features, serialized_bytes_vec.as_vector(),
             wire_bytes_vec, compile_time_imports, {})
             .ToHandle(&module_object)) {
      // Deserialization failed, probably because of mismatched compile time
      // imports. Invalid wire bytes are unlikely because of the hash check
      // above.
      ThrowError(isolate,
                 "Deserialization failed, probably because of mismatched "
                 "compile time imports");
      return;
    }
  }

  info.GetReturnValue().Set(Utils::ToLocal(module_object));
}

#endif  // V8_ENABLE_WEBASSEMBLY

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) {
  Isolate* isolate = info.GetIsolate();
  for (int i = first_arg_index; i < info.Length(); i++) {
    HandleScope handle_scope(isolate);
    if (i != first_arg_index) {
      fprintf(file, " ");
    }

    // Explicitly catch potential exceptions in toString().
    v8::TryCatch try_catch(isolate);
    Local<Value> arg = info[i];
    Local<String> str_obj;

    if (arg->IsSymbol()) {
      arg = arg.As<Symbol>()->Description(isolate);
    }
    if (!arg->ToString(isolate->GetCurrentContext()).ToLocal(&str_obj)) {
      try_catch.ReThrow();
      return;
    }

    v8::String::Utf8Value str(isolate, str_obj);
    size_t n = 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));
  Isolate* isolate = info.GetIsolate();
  String::Utf8Value file_name(isolate, info[0]);
  if (*file_name == nullptr) {
    ThrowError(isolate, "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(isolate, "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(isolate, "Error opening file");
      return;
    }
    WriteToFile(file, info, 1);
  }
  base::Fclose(file);
}

void Shell::ReadFile(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate = info.GetIsolate();
  String::Utf8Value file_name(isolate, info[0]);
  if (*file_name == nullptr) {
    ThrowError(isolate, "Error converting filename to string");
    return;
  }
  if (info.Length() == 2) {
    String::Utf8Value format(isolate, info[1]);
    if (*format && std::strcmp(*format, "binary") == 0) {
      ReadBuffer(info);
      return;
    }
  }
  Local<String> source;
  if (!ReadFile(isolate, *file_name).ToLocal(&source)) return;
  info.GetReturnValue().Set(source);
}

#if V8_TARGET_OS_LINUX && V8_ENABLE_WEBASSEMBLY
void Shell::CreateWasmMemoryMapDescriptor(
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  Isolate* isolate = info.GetIsolate();
  CHECK(i::v8_flags.experimental_wasm_memory_control);
  DCHECK(i::ValidateCallbackInfo(info));
  String::Utf8Value file_name(isolate, info[0]);
  if (*file_name == nullptr) {
    ThrowError(isolate, "Error converting filename to string");
    return;
  }

  int file_descriptor = open(*file_name, O_RDWR);

  WasmMemoryMapDescriptor::WasmFileDescriptor wasm_fd =
      static_cast<WasmMemoryMapDescriptor::WasmFileDescriptor>(file_descriptor);
  info.GetReturnValue().Set(v8::WasmMemoryMapDescriptor::New(isolate, wasm_fd));
}
#endif  // V8_TARGET_OS_LINUX

MaybeLocal<String> Shell::ReadFromStdin(Isolate* isolate) {
  static const int kBufferSize = 256;
  char buffer[kBufferSize];
  Local<String> accumulator = String::NewFromUtf8Literal(isolate, "");
  // 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, throw and give up.
    char* input = nullptr;
    input = fgets(buffer, kBufferSize, stdin);
    if (ferror(stdin)) {
      ThrowError(isolate, "Error while reading from stdin");
      return {};
    }
    if (input == nullptr) {
      if (accumulator->Length() == 0) return {};
      return accumulator;
    }
    int length = base::checked_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());
    }
    if (accumulator.IsEmpty()) {
      ThrowError(isolate, "String limit exceeded");
      return {};
    }
  }
}

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);
    String::Utf8Value file_name(isolate, info[i]);
    if (*file_name == nullptr) {
      std::ostringstream oss;
      oss << "Cannot convert file[" << i << "] name to string.";
      ThrowError(isolate, oss.view());
      return;
    }
    Local<String> source;
    if (!ReadFile(isolate, *file_name).ToLocal(&source)) return;
    if (!ExecuteString(
            isolate, source,
            String::NewFromUtf8(isolate, *file_name).ToLocalChecked(),
            options.quiet_load ? kNoReportExceptions : kReportExceptions)) {
      std::ostringstream oss;
      oss << "Error executing file: \"" << *file_name << '"';
      ThrowError(isolate, oss.view());
      return;
    }
  }
}

class SetTimeoutTask : public v8::Task {
 public:
  SetTimeoutTask(v8::Isolate* isolate, v8::Local<v8::Context> context,
                 v8::Local<v8::Function> callback)
      : isolate_(isolate),
        context_(isolate, context),
        callback_(isolate, callback) {}

  void Run() override {
    HandleScope scope(isolate_);
    Local<Context> context = context_.Get(isolate_);
    Local<Function> callback = callback_.Get(isolate_);
    Context::Scope context_scope(context);
    MaybeLocal<Value> result =
        callback->Call(context, Undefined(isolate_), 0, nullptr);
    USE(result);
  }

 private:
  v8::Isolate* isolate_;
  v8::Global<v8::Context> context_;
  v8::Global<v8::Function> callback_;
};

void Shell::SetTimeout(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate = info.GetIsolate();
  info.GetReturnValue().Set(v8::Number::New(isolate, 0));
  if (info.Length() == 0 || !info[0]->IsFunction()) return;
  Local<Function> callback = info[0].As<Function>();
  Local<Context> context = isolate->GetCurrentContext();
  g_platform->GetForegroundTaskRunner(isolate)->PostTask(
      std::make_unique<SetTimeoutTask>(isolate, context, callback));
}

#ifdef V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA
void Shell::GetContinuationPreservedEmbedderData(
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  Isolate* isolate = info.GetIsolate();
  Local<Data> data = isolate->GetContinuationPreservedEmbedderDataV2();
  DCHECK(!data.IsEmpty());
  if (!data->IsValue()) {
    data = Undefined(isolate);
  }
  DCHECK(!data.IsEmpty());
  info.GetReturnValue().Set(Local<Value>::Cast(data));
}
#endif  // V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA

void Shell::GetExtrasBindingObject(
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  Local<Context> context = info.GetIsolate()->GetCurrentContext();
  info.GetReturnValue().Set(context->GetExtrasBindingObject());
}

void Shell::ReadCodeTypeAndArguments(
    const v8::FunctionCallbackInfo<v8::Value>& info, int index,
    CodeType* code_type, Local<Value>* arguments) {
  Isolate* isolate = info.GetIsolate();
  if (info.Length() > index && info[index]->IsObject()) {
    Local<Object> object = info[index].As<Object>();
    Local<Context> context = isolate->GetCurrentContext();
    Local<Value> value;
    if (!TryGetValue(isolate, context, object, "type").ToLocal(&value)) {
      *code_type = CodeType::kNone;
      return;
    }
    if (!value->IsString()) {
      *code_type = CodeType::kInvalid;
      return;
    }
    Local<String> worker_type_string =
        value->ToString(context).ToLocalChecked();
    String::Utf8Value str(isolate, worker_type_string);
    if (strcmp("classic", *str) == 0) {
      *code_type = CodeType::kFileName;
    } else if (strcmp("string", *str) == 0) {
      *code_type = CodeType::kString;
    } else if (strcmp("function", *str) == 0) {
      *code_type = CodeType::kFunction;
    } else {
      *code_type = CodeType::kInvalid;
    }
    if (arguments != nullptr) {
      bool got_arguments =
          TryGetValue(isolate, context, object, "arguments").ToLocal(arguments);
      USE(got_arguments);
    }
  } else {
    *code_type = CodeType::kNone;
  }
}

bool Shell::FunctionAndArgumentsToString(Local<Function> function,
                                         Local<Value> arguments,
                                         Local<String>* source,
                                         Isolate* isolate) {
  Local<Context> context = isolate->GetCurrentContext();
  MaybeLocal<String> maybe_function_string =
      function->FunctionProtoToString(context);
  Local<String> function_string;
  if (!maybe_function_string.ToLocal(&function_string)) {
    ThrowError(isolate, "Failed to convert function to string");
    return false;
  }
  *source = String::NewFromUtf8Literal(isolate, "(");
  *source = String::Concat(isolate, *source, function_string);
  if (source->IsEmpty()) {
    ThrowError(isolate, "String limit exceeded");
    return false;
  }
  Local<String> middle = String::NewFromUtf8Literal(isolate, ")(");
  *source = String::Concat(isolate, *source, middle);
  if (source->IsEmpty()) {
    ThrowError(isolate, "String limit exceeded");
    return false;
  }
  if (!arguments.IsEmpty() && !arguments->IsUndefined()) {
    if (!arguments->IsArray()) {
      ThrowError(isolate, "'arguments' must be an array");
      return false;
    }
    Local<String> comma = String::NewFromUtf8Literal(isolate, ",");
    Local<Array> array = arguments.As<Array>();
    for (uint32_t i = 0; i < array->Length(); ++i) {
      if (i > 0) {
        *source = String::Concat(isolate, *source, comma);
        if (source->IsEmpty()) {
          ThrowError(isolate, "String limit exceeded");
          return false;
        }
      }
      MaybeLocal<Value> maybe_argument = array->Get(context, i);
      Local<Value> argument;
      if (!maybe_argument.ToLocal(&argument)) {
        ThrowError(isolate, "Failed to get argument");
        return false;
      }
      Local<String> argument_string;
      if (!JSON::Stringify(context, argument).ToLocal(&argument_string)) {
        ThrowError(isolate, "Failed to convert argument to string");
        return false;
      }
      *source = String::Concat(isolate, *source, argument_string);
      if (source->IsEmpty()) {
        ThrowError(isolate, "String limit exceeded");
        return false;
      }
    }
  }
  Local<String> suffix = String::NewFromUtf8Literal(isolate, ")");
  *source = String::Concat(isolate, *source, suffix);
  if (source->IsEmpty()) {
    ThrowError(isolate, "String limit exceeded");
    return false;
  }
  return true;
}

// ReadSource() supports reading source code through `info[index]` as specified
// by the `default_type` or an optional options bag provided in `info[index+1]`
// (e.g. `options={type: 'code_type', arguments:[...]}`).
MaybeLocal<String> Shell::ReadSource(
    const v8::FunctionCallbackInfo<v8::Value>& info, int index,
    CodeType default_type) {
  CodeType code_type;
  Local<Value> arguments;
  ReadCodeTypeAndArguments(info, index + 1, &code_type, &arguments);

  Isolate* isolate = info.GetIsolate();
  Local<String> source;
  if (code_type == CodeType::kNone) {
    code_type = default_type;
  }
  switch (code_type) {
    case CodeType::kFunction:
      if (!info[index]->IsFunction()) {
        return MaybeLocal<String>();
      }
      // Source: ( function_to_string )( params )
      if (!FunctionAndArgumentsToString(info[index].As<Function>(), arguments,
                                        &source, isolate)) {
        return MaybeLocal<String>();
      }
      break;
    case CodeType::kFileName: {
      if (!info[index]->IsString()) {
        return MaybeLocal<String>();
      }
      String::Utf8Value filename(isolate, info[index]);
      if (!Shell::ReadFile(isolate, *filename).ToLocal(&source)) {
        return MaybeLocal<String>();
      }
      break;
    }
    case CodeType::kString:
      if (!info[index]->IsString()) {
        return MaybeLocal<String>();
      }
      source = info[index].As<String>();
      break;
    case CodeType::kNone:
    case CodeType::kInvalid:
      return MaybeLocal<String>();
  }
  return source;
}

void Shell::WorkerNew(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate = info.GetIsolate();
  HandleScope handle_scope(isolate);
  if (info.Length() < 1 || (!info[0]->IsString() && !info[0]->IsFunction())) {
    ThrowError(isolate, "1st argument must be a string or a function");
    return;
  }

  Local<String> source;
  if (!ReadSource(info, 0, CodeType::kFileName).ToLocal(&source)) {
    ThrowError(isolate, "Invalid argument");
    return;
  }

  if (!info.IsConstructCall()) {
    ThrowError(isolate, "Worker must be constructed with new");
    return;
  }

  // Initialize the embedder field to 0; if we return early without
  // creating a new Worker (because the main thread is terminating) we can
  // early-out from the instance calls.
  info.This()->SetInternalField(0, v8::Integer::New(isolate, 0));

  // By default, propagate flush denormals state.
  bool flush_denormals = base::FPU::GetFlushDenormals();
  if (info.Length() > 1 && info[1]->IsObject()) {
    Local<Value> value;
    if (!TryGetValue(isolate, isolate->GetCurrentContext(),
                     info[1].As<Object>(), "flushDenormals")
             .ToLocal(&value)) {
      return;
    }
    if (!value->IsUndefined()) {
      flush_denormals = value->BooleanValue(isolate);
    }
  }

  {
    // Don't allow workers to create more workers if the main thread
    // is waiting for existing running workers to terminate.
    i::ParkedMutexGuard lock_guard(
        reinterpret_cast<i::Isolate*>(isolate)->main_thread_local_isolate(),
        workers_mutex_.Pointer());
    if (!allow_new_workers_) return;

    String::Utf8Value script(isolate, source);
    if (!*script) {
      ThrowError(isolate, "Can't get worker script");
      return;
    }

    // The C++ worker object's lifetime is shared between the Managed<Worker>
    // object on the heap, which the JavaScript object points to, and an
    // internal std::shared_ptr in the worker thread itself.
    auto worker = std::make_shared<Worker>(isolate, *script, flush_denormals);
    i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
    const size_t kWorkerSizeEstimate = 4 * 1024 * 1024;  // stack + heap.
    i::DirectHandle<i::Object> managed =
        i::Managed<Worker>::From(i_isolate, kWorkerSizeEstimate, worker);
    info.This()->SetInternalField(0, Utils::ToLocal(managed));
    base::Thread::Priority priority =
        options.apply_priority ? base::Thread::Priority::kUserBlocking
                               : base::Thread::Priority::kDefault;
    if (!Worker::StartWorkerThread(isolate, worker, priority)) {
      ThrowError(isolate, "Can't start thread");
      return;
    }
    PerIsolateData::Get(isolate)->RegisterWorker(worker);
  }
}

void Shell::WorkerPostMessage(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate = info.GetIsolate();
  HandleScope handle_scope(isolate);

  if (info.Length() < 1) {
    ThrowError(isolate, "Invalid argument");
    return;
  }

  std::shared_ptr<Worker> worker =
      GetWorkerFromInternalField(isolate, info.This());
  if (!worker.get()) {
    return;
  }

  Local<Value> message = info[0];
  Local<Value> transfer =
      info.Length() >= 2 ? info[1] : Undefined(isolate).As<Value>();
  std::unique_ptr<SerializationData> data =
      Shell::SerializeValue(isolate, message, transfer);
  if (data) {
    worker->PostMessage(std::move(data));
  }
}

void Shell::WorkerGetMessage(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate = info.GetIsolate();
  HandleScope handle_scope(isolate);
  std::shared_ptr<Worker> worker =
      GetWorkerFromInternalField(isolate, info.This());
  if (!worker.get()) {
    return;
  }

  std::unique_ptr<SerializationData> data = worker->GetMessage(isolate);
  if (data) {
    Local<Value> value;
    if (Shell::DeserializeValue(isolate, std::move(data)).ToLocal(&value)) {
      info.GetReturnValue().Set(value);
    }
  }
}

// Task processing one onmessage event received from a Worker.
class OnMessageFromWorkerTask : public v8::Task {
 public:
  OnMessageFromWorkerTask(v8::Isolate* isolate, v8::Local<v8::Context> context,
                          v8::Local<v8::Value> callback,
                          std::unique_ptr<SerializationData> data)
      : isolate_(isolate),
        context_(isolate, context),
        callback_(isolate, callback),
        data_(std::move(data)) {}

  void Run() override {
    HandleScope scope(isolate_);
    Local<Context> context = context_.Get(isolate_);
    Context::Scope context_scope(context);
    MicrotasksScope microtasks_scope(context,
                                     MicrotasksScope::kDoNotRunMicrotasks);

    Local<Object> global = context->Global();

    // Get the message handler.
    Local<Value> onmessage = callback_.Get(isolate_);
    if (!onmessage->IsFunction()) return;
    Local<Function> onmessage_fun = onmessage.As<Function>();

    v8::TryCatch try_catch(isolate_);
    try_catch.SetVerbose(true);
    Local<Value> value;
    if (Shell::DeserializeValue(isolate_, std::move(data_)).ToLocal(&value)) {
      DCHECK(!isolate_->IsExecutionTerminating());
      Local<Object> event = v8::Object::New(isolate_);
      event
          ->CreateDataProperty(
              context,
              String::NewFromUtf8Literal(isolate_, "data",
                                         NewStringType::kInternalized),
              value)
          .ToChecked();
      Local<Value> argv[] = {event};
      MaybeLocal<Value> result = onmessage_fun->Call(context, global, 1, argv);
      USE(result);
    }
  }

 private:
  v8::Isolate* isolate_;
  v8::Global<v8::Context> context_;
  v8::Global<v8::Value> callback_;
  std::unique_ptr<SerializationData> data_;
};

// Check, on the main thread, whether a worker has any enqueued any message
// events. Workers post this task when posting a message, instead of posting
// OnMessageFromWorkerTask directly, to avoid races between message posting
// and onmessage subscription.
class CheckMessageFromWorkerTask : public v8::Task {
 public:
  CheckMessageFromWorkerTask(v8::Isolate* isolate,
                             std::shared_ptr<Worker> worker)
      : isolate_(isolate), worker_(std::move(worker)) {}

  void Run() override {
    HandleScope scope(isolate_);

    // Get the callback for onmessage events from this worker. It's important to
    // do this here, and not in OnMessageFromWorkerTask, because we may get a
    // CleanUpWorkerTask scheduled before the posted OnMessageFromWorkerTask
    // executes, which will
    auto callback_pair =
        PerIsolateData::Get(isolate_)->GetWorkerOnMessage(worker_);
    // Bail out if there's no callback -- leave the message queue untouched so
    // that we don't lose the messages and can read them with GetMessage later.
    // This is slightly different to browser behaviour, where events can be
    // missed, but it's helpful for d8's GetMessage behaviour.
    if (callback_pair.second.IsEmpty()) return;

    std::unique_ptr<SerializationData> result;
    while ((result = worker_->TryGetMessage())) {
      // Each onmessage callback call is posted as a separate task.
      g_platform->GetForegroundTaskRunner(isolate_)->PostTask(
          std::make_unique<OnMessageFromWorkerTask>(
              isolate_, callback_pair.first, callback_pair.second,
              std::move(result)));
    }
  }

 private:
  v8::Isolate* isolate_;
  std::shared_ptr<Worker> worker_;
};

// Unregister the given isolate from message events from the given worker.
// This must be done before the isolate or worker are destroyed, so that the
// global handles for context and callback are cleaned up correctly -- thus the
// event loop blocks until all workers are unregistered.
class CleanUpWorkerTask : public v8::Task {
 public:
  CleanUpWorkerTask(v8::Isolate* isolate, std::shared_ptr<Worker> worker)
      : isolate_(isolate), worker_(std::move(worker)) {}

  void Run() override {
    PerIsolateData::Get(isolate_)->UnregisterWorker(std::move(worker_));
  }

 private:
  v8::Isolate* isolate_;
  std::shared_ptr<Worker> worker_;
};

void Shell::WorkerOnMessageGetter(
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate = info.GetIsolate();
  HandleScope handle_scope(isolate);

  std::shared_ptr<Worker> worker =
      GetWorkerFromInternalField(isolate, info.This());
  if (!worker.get()) {
    return;
  }
  Local<Function> callback =
      PerIsolateData::Get(isolate)->GetWorkerOnMessage(worker).second;

  if (!callback.IsEmpty()) {
    info.GetReturnValue().Set(callback);
  }
}

void Shell::WorkerOnMessageSetter(
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate = info.GetIsolate();
  HandleScope handle_scope(isolate);

  if (info.Length() < 1) {
    ThrowError(isolate, "Invalid argument");
    return;
  }

  std::shared_ptr<Worker> worker =
      GetWorkerFromInternalField(isolate, info.This());
  if (!worker.get()) {
    return;
  }

  Local<Value> callback = info[0];
  if (!callback->IsFunction()) return;

  PerIsolateData::Get(isolate)->SubscribeWorkerOnMessage(
      worker, isolate->GetCurrentContext(), Local<Function>::Cast(callback));
}

void Shell::WorkerTerminate(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate = info.GetIsolate();
  HandleScope handle_scope(isolate);
  std::shared_ptr<Worker> worker =
      GetWorkerFromInternalField(isolate, info.This());
  if (!worker.get()) return;
  worker->Terminate();
}

void Shell::WorkerTerminateAndWait(
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate = info.GetIsolate();
  HandleScope handle_scope(isolate);
  std::shared_ptr<Worker> worker =
      GetWorkerFromInternalField(isolate, info.This());
  if (!worker.get()) {
    return;
  }

  reinterpret_cast<i::Isolate*>(isolate)
      ->main_thread_local_isolate()
      ->ExecuteMainThreadWhileParked([worker](const i::ParkedScope& parked) {
        worker->TerminateAndWaitForThread(parked);
      });
}

void Shell::QuitOnce(v8::FunctionCallbackInfo<v8::Value>* info) {
  Isolate* isolate = info->GetIsolate();
  int exit_code =
      (*info)[0]->Int32Value(isolate->GetCurrentContext()).FromMaybe(0);
  ResetOnProfileEndListener(isolate);

  // As we exit the process anyway, we do not dispose the platform and other
  // global data and manually unlock to quell DCHECKs. Other isolates might
  // still be running, so disposing here can cause them to crash.
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  if (i_isolate->thread_manager()->IsLockedByCurrentThread()) {
    i_isolate->thread_manager()->Unlock();
  }

  // When disposing the shared space isolate, the workers (client isolates) need
  // to be terminated first.
  if (i_isolate->is_shared_space_isolate()) {
    i_isolate->main_thread_local_isolate()->ExecuteMainThreadWhileParked(
        [](const i::ParkedScope& parked) { WaitForRunningWorkers(parked); });
  }

  // Reset thread-locals here only after stopping workers. They are still used
  // e.g. during heap verification in shared GCs.
  isolate->Exit();

  OnExit(isolate, false);
  base::OS::ExitProcess(exit_code);
}

namespace {

bool SkipTerminationForFuzzing() {
  // Triggering termination from JS can cause some non-determinism thus we
  // skip it for correctness fuzzing.
  if (i::v8_flags.correctness_fuzzer_suppressions) return true;
  // Termination also currently breaks Fuzzilli's REPRL mechanism as the
  // scheduled termination will prevent the next testcase sent by Fuzzilli from
  // being processed. This will in turn desynchronize the communication
  // between d8 and Fuzzilli, leading to a crash.
  if (fuzzilli_reprl) return true;
  return false;
}

}  // namespace

void Shell::TerminateNow(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  if (SkipTerminationForFuzzing()) return;
  auto v8_isolate = info.GetIsolate();
  if (!v8_isolate->IsExecutionTerminating()) {
    // Force a termination exception for immediate termination.
    i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
    i_isolate->TerminateExecution();
  }
}

void Shell::ScheduleTermination(
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  if (SkipTerminationForFuzzing()) return;
  auto v8_isolate = info.GetIsolate();
  // Schedule a termination request, handled by an interrupt later.
  if (!v8_isolate->IsExecutionTerminating()) v8_isolate->TerminateExecution();
}

void Shell::Quit(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  base::CallOnce(&quit_once_, &QuitOnce,
                 const_cast<v8::FunctionCallbackInfo<v8::Value>*>(&info));
}

void Shell::Version(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  info.GetReturnValue().Set(
      String::NewFromUtf8(info.GetIsolate(), V8::GetVersion())
          .ToLocalChecked());
}

void Shell::ReportException(Isolate* isolate, Local<v8::Message> message,
                            Local<v8::Value> exception_obj) {
  HandleScope handle_scope(isolate);
  Local<Context> context = isolate->GetCurrentContext();
  bool enter_context = context.IsEmpty();
  if (enter_context) {
    context = Local<Context>::New(isolate, evaluation_context_);
    context->Enter();
  }
  // Converts a V8 value to a C string.
  auto ToCString = [](const v8::String::Utf8Value& value) {
    return *value ? *value : "<string conversion failed>";
  };

  v8::String::Utf8Value exception(isolate, exception_obj);
  const char* exception_string = ToCString(exception);
  if (message.IsEmpty()) {
    // V8 didn't provide any extra information about this error; just
    // print the exception.
    printf("%s\n", exception_string);
  } else if (message->GetScriptOrigin().Options().IsWasm()) {
    // Print wasm-function[(function index)]:(offset): (message).
    int function_index = message->GetWasmFunctionIndex();
    int offset = message->GetStartColumn(context).FromJust();
    printf("wasm-function[%d]:0x%x: %s\n", function_index, offset,
           exception_string);
  } else {
    // Print (filename):(line number): (message).
    v8::String::Utf8Value filename(isolate,
                                   message->GetScriptOrigin().ResourceName());
    const char* filename_string = ToCString(filename);
    int linenum = message->GetLineNumber(context).FromMaybe(-1);
    printf("%s:%i: %s\n", filename_string, linenum, exception_string);
    Local<String> sourceline;
    if (message->GetSourceLine(context).ToLocal(&sourceline)) {
      // Print line of source code.
      v8::String::Utf8Value sourcelinevalue(isolate, sourceline);
      const char* sourceline_string = ToCString(sourcelinevalue);
      printf("%s\n", sourceline_string);
      // Print wavy underline (GetUnderline is deprecated).
      int start = message->GetStartColumn(context).FromJust();
      for (int i = 0; i < start; i++) {
        printf(" ");
      }
      int end = message->GetEndColumn(context).FromJust();
      for (int i = start; i < end; i++) {
        printf("^");
      }
      printf("\n");
    }
  }
  Local<Value> stack_trace_string;
  if (v8::TryCatch::StackTrace(context, exception_obj)
          .ToLocal(&stack_trace_string) &&
      stack_trace_string->IsString()) {
    v8::String::Utf8Value stack_trace(isolate, stack_trace_string.As<String>());
    printf("%s\n", ToCString(stack_trace));
  }
  printf("\n");
  if (enter_context) context->Exit();
}

void Shell::ReportException(v8::Isolate* isolate,
                            const v8::TryCatch& try_catch) {
  if (isolate->IsExecutionTerminating()) {
    printf("Got Execution Termination Exception\n");
  } else {
    ReportException(isolate, try_catch.Message(), try_catch.Exception());
  }
}

void Counter::Bind(const char* name, bool is_histogram) {
  base::OS::StrNCpy(name_, kMaxNameSize, name, kMaxNameSize);
  // Explicitly null-terminate, in case {name} is longer than {kMaxNameSize}.
  name_[kMaxNameSize - 1] = '\0';
  is_histogram_ = is_histogram;
}

void Counter::AddSample(int sample) {
  count_.fetch_add(1, std::memory_order_relaxed);
  sample_total_.fetch_add(sample, std::memory_order_relaxed);
}

CounterCollection::CounterCollection() {
  magic_number_ = 0xDEADFACE;
  max_counters_ = kMaxCounters;
  max_name_size_ = Counter::kMaxNameSize;
  counters_in_use_ = 0;
}

Counter* CounterCollection::GetNextCounter() {
  if (counters_in_use_ == kMaxCounters) return nullptr;
  return &counters_[counters_in_use_++];
}

void Shell::MapCounters(v8::Isolate* isolate, const char* name) {
  counters_file_ = base::OS::MemoryMappedFile::create(
      name, sizeof(CounterCollection), &local_counters_);
  void* memory =
      (counters_file_ == nullptr) ? nullptr : counters_file_->memory();
  if (memory == nullptr) {
    printf("Could not map counters file %s\n", name);
    base::OS::ExitProcess(1);
  }
  counters_ = static_cast<CounterCollection*>(memory);
}

Counter* Shell::GetCounter(const char* name, bool is_histogram) {
  Counter* counter = nullptr;
  {
    base::MutexGuard mutex_guard(&counter_mutex_);
    auto map_entry = counter_map_->find(name);
    if (map_entry != counter_map_->end()) {
      counter = map_entry->second;
    }
  }

  if (counter == nullptr) {
    base::MutexGuard mutex_guard(&counter_mutex_);

    counter = (*counter_map_)[name];

    if (counter == nullptr) {
      counter = counters_->GetNextCounter();
      if (counter == nullptr) {
        // Too many counters.
        return nullptr;
      }
      (*counter_map_)[name] = counter;
      counter->Bind(name, is_histogram);
    }
  }

  DCHECK_EQ(is_histogram, counter->is_histogram());
  return counter;
}

int* Shell::LookupCounter(const char* name) {
  Counter* counter = GetCounter(name, false);
  return counter ? counter->ptr() : nullptr;
}

void* Shell::CreateHistogram(const char* name, int min, int max,
                             size_t buckets) {
  return GetCounter(name, true);
}

void Shell::AddHistogramSample(void* histogram, int sample) {
  Counter* counter = reinterpret_cast<Counter*>(histogram);
  counter->AddSample(sample);
}

// Turn a value into a human-readable string.
Local<String> Shell::Stringify(Isolate* isolate, Local<Value> value) {
  v8::Local<v8::Context> context =
      v8::Local<v8::Context>::New(isolate, evaluation_context_);
  if (stringify_function_.IsEmpty()) {
    Local<String> source =
        String::NewFromUtf8(isolate, stringify_source_).ToLocalChecked();
    Local<String> name = String::NewFromUtf8Literal(isolate, "d8-stringify");
    ScriptOrigin origin(name);
    Local<Script> script =
        Script::Compile(context, source, &origin).ToLocalChecked();
    stringify_function_.Reset(
        isolate, script->Run(context).ToLocalChecked().As<Function>());
  }
  Local<Function> fun = Local<Function>::New(isolate, stringify_function_);
  Local<Value> argv[1] = {value};
  v8::TryCatch try_catch(isolate);
  MaybeLocal<Value> result = fun->Call(context, Undefined(isolate), 1, argv);
  if (result.IsEmpty()) return String::Empty(isolate);
  return result.ToLocalChecked().As<String>();
}

void Shell::NodeTypeCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  v8::Isolate* isolate = info.GetIsolate();

  info.GetReturnValue().Set(v8::Number::New(isolate, 1));
}

enum class JSApiInstanceType : uint16_t {
  kGenericApiObject = 0,  // FunctionTemplateInfo::kNoJSApiObjectType.
  kEventTarget,
  kNode,
  kElement,
  kHTMLElement,
  kHTMLDivElement,
};

Local<FunctionTemplate> NewDOMFunctionTemplate(
    Isolate* isolate, JSApiInstanceType instance_type) {
  return FunctionTemplate::New(
      isolate, nullptr, Local<Value>(), Local<Signature>(), 0,
      ConstructorBehavior::kAllow, SideEffectType::kHasSideEffect, nullptr,
      static_cast<uint16_t>(instance_type));
}

Local<FunctionTemplate> Shell::CreateEventTargetTemplate(Isolate* isolate) {
  Local<FunctionTemplate> event_target =
      NewDOMFunctionTemplate(isolate, JSApiInstanceType::kEventTarget);
  return event_target;
}

Local<FunctionTemplate> Shell::CreateNodeTemplates(
    Isolate* isolate, Local<FunctionTemplate> event_target) {
  Local<FunctionTemplate> node =
      NewDOMFunctionTemplate(isolate, JSApiInstanceType::kNode);
  node->Inherit(event_target);

  PerIsolateData* data = PerIsolateData::Get(isolate);
  data->SetDomNodeCtor(node);

  Local<ObjectTemplate> proto_template = node->PrototypeTemplate();
  Local<Signature> signature = v8::Signature::New(isolate, node);
  Local<FunctionTemplate> nodeType = FunctionTemplate::New(
      isolate, NodeTypeCallback, Local<Value>(), signature, 0,
      ConstructorBehavior::kThrow, SideEffectType::kHasSideEffect, nullptr,
      static_cast<uint16_t>(JSApiInstanceType::kGenericApiObject),
      static_cast<uint16_t>(JSApiInstanceType::kElement),
      static_cast<uint16_t>(JSApiInstanceType::kHTMLDivElement));
  nodeType->SetAcceptAnyReceiver(false);
  proto_template->SetAccessorProperty(
      String::NewFromUtf8Literal(isolate, "nodeType"), nodeType);

  Local<FunctionTemplate> element =
      NewDOMFunctionTemplate(isolate, JSApiInstanceType::kElement);
  element->Inherit(node);

  Local<FunctionTemplate> html_element =
      NewDOMFunctionTemplate(isolate, JSApiInstanceType::kHTMLElement);
  html_element->Inherit(element);

  Local<FunctionTemplate> div_element =
      NewDOMFunctionTemplate(isolate, JSApiInstanceType::kHTMLDivElement);
  div_element->Inherit(html_element);

  return div_element;
}

Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
  Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
  global_template->Set(Symbol::GetToStringTag(isolate),
                       String::NewFromUtf8Literal(isolate, "global"));
  global_template->Set(isolate, "version",
                       FunctionTemplate::New(isolate, Version));

  global_template->Set(isolate, "print", FunctionTemplate::New(isolate, Print));
  global_template->Set(isolate, "printErr",
                       FunctionTemplate::New(isolate, PrintErr));
  global_template->Set(isolate, "write",
                       FunctionTemplate::New(isolate, WriteStdout));
  if (!i::v8_flags.fuzzing) {
    global_template->Set(isolate, "writeFile",
                         FunctionTemplate::New(isolate, WriteFile));
  }
  global_template->Set(isolate, "read",
                       FunctionTemplate::New(isolate, ReadFile));
  global_template->Set(isolate, "readbuffer",
                       FunctionTemplate::New(isolate, ReadBuffer));
  global_template->Set(isolate, "readline",
                       FunctionTemplate::New(isolate, ReadLine));
  global_template->Set(isolate, "load",
                       FunctionTemplate::New(isolate, ExecuteFile));
  global_template->Set(isolate, "setTimeout",
                       FunctionTemplate::New(isolate, SetTimeout));
  // Some Emscripten-generated code tries to call 'quit', which in turn would
  // call C's exit(). This would lead to memory leaks, because there is no way
  // we can terminate cleanly then, so we need a way to hide 'quit'.
  if (!options.omit_quit) {
    global_template->Set(isolate, "quit", FunctionTemplate::New(isolate, Quit));
  }
  global_template->Set(isolate, "Realm", Shell::CreateRealmTemplate(isolate));
  global_template->Set(isolate, "performance",
                       Shell::CreatePerformanceTemplate(isolate));
  global_template->Set(isolate, "Worker", Shell::CreateWorkerTemplate(isolate));

  // Prevent fuzzers from creating side effects.
  if (!i::v8_flags.fuzzing) {
    global_template->Set(isolate, "os", Shell::CreateOSTemplate(isolate));
  }
  global_template->Set(isolate, "d8", Shell::CreateD8Template(isolate));

  if (i::v8_flags.expose_async_hooks) {
    global_template->Set(isolate, "async_hooks",
                         Shell::CreateAsyncHookTemplate(isolate));
  }

  return global_template;
}

Local<ObjectTemplate> Shell::CreateOSTemplate(Isolate* isolate) {
  Local<ObjectTemplate> os_template = ObjectTemplate::New(isolate);
  AddOSMethods(isolate, os_template);
  os_template->Set(isolate, "name",
                   v8::String::NewFromUtf8Literal(isolate, V8_TARGET_OS_STRING),
                   PropertyAttribute::ReadOnly);
  os_template->Set(
      isolate, "d8Path",
      v8::String::NewFromUtf8(isolate, options.d8_path).ToLocalChecked(),
      PropertyAttribute::ReadOnly);
  return os_template;
}

Local<FunctionTemplate> Shell::CreateWorkerTemplate(Isolate* isolate) {
  Local<FunctionTemplate> worker_fun_template =
      FunctionTemplate::New(isolate, WorkerNew);
  Local<Signature> worker_signature =
      Signature::New(isolate, worker_fun_template);
  worker_fun_template->SetClassName(
      String::NewFromUtf8Literal(isolate, "Worker"));
  worker_fun_template->ReadOnlyPrototype();
  worker_fun_template->PrototypeTemplate()->Set(
      isolate, "terminate",
      FunctionTemplate::New(isolate, WorkerTerminate, Local<Value>(),
                            worker_signature));
  worker_fun_template->PrototypeTemplate()->Set(
      isolate, "terminateAndWait",
      FunctionTemplate::New(isolate, WorkerTerminateAndWait, Local<Value>(),
                            worker_signature));
  worker_fun_template->PrototypeTemplate()->Set(
      isolate, "postMessage",
      FunctionTemplate::New(isolate, WorkerPostMessage, Local<Value>(),
                            worker_signature));
  worker_fun_template->PrototypeTemplate()->Set(
      isolate, "getMessage",
      FunctionTemplate::New(isolate, WorkerGetMessage, Local<Value>(),
                            worker_signature));
  worker_fun_template->PrototypeTemplate()->SetAccessorProperty(
      String::NewFromUtf8(isolate, "onmessage", NewStringType::kInternalized)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, WorkerOnMessageGetter, Local<Value>(),
                            worker_signature),
      FunctionTemplate::New(isolate, WorkerOnMessageSetter, Local<Value>(),
                            worker_signature));
  worker_fun_template->InstanceTemplate()->SetInternalFieldCount(1);
  return worker_fun_template;
}

Local<ObjectTemplate> Shell::CreateAsyncHookTemplate(Isolate* isolate) {
  Local<ObjectTemplate> async_hooks_templ = ObjectTemplate::New(isolate);
  async_hooks_templ->Set(isolate, "createHook",
                         FunctionTemplate::New(isolate, AsyncHooksCreateHook));
  async_hooks_templ->Set(
      isolate, "executionAsyncId",
      FunctionTemplate::New(isolate, AsyncHooksExecutionAsyncId));
  async_hooks_templ->Set(
      isolate, "triggerAsyncId",
      FunctionTemplate::New(isolate, AsyncHooksTriggerAsyncId));
  return async_hooks_templ;
}

Local<ObjectTemplate> Shell::CreatePerformanceTemplate(Isolate* isolate) {
  Local<ObjectTemplate> performance_template = ObjectTemplate::New(isolate);
  if (i::v8_flags.correctness_fuzzer_suppressions) {
    // Stubs for differential fuzzing.
    performance_template->Set(isolate, "now",
                              FunctionTemplate::New(isolate, PerformanceStub));
    performance_template->Set(isolate, "mark",
                              FunctionTemplate::New(isolate, PerformanceStub));
    performance_template->Set(isolate, "measure",
                              FunctionTemplate::New(isolate, PerformanceStub));
    performance_template->Set(isolate, "measureMemory",
                              FunctionTemplate::New(isolate, PerformanceStub));
  } else {
    performance_template->Set(isolate, "now",
                              FunctionTemplate::New(isolate, PerformanceNow));
    performance_template->Set(isolate, "mark",
                              FunctionTemplate::New(isolate, PerformanceMark));
    performance_template->Set(
        isolate, "measure", FunctionTemplate::New(isolate, PerformanceMeasure));
    performance_template->Set(
        isolate, "measureMemory",
        FunctionTemplate::New(isolate, PerformanceMeasureMemory));
  }
  return performance_template;
}

Local<ObjectTemplate> Shell::CreateRealmTemplate(Isolate* isolate) {
  Local<ObjectTemplate> realm_template = ObjectTemplate::New(isolate);
  realm_template->Set(isolate, "current",
                      FunctionTemplate::New(isolate, RealmCurrent));
  realm_template->Set(isolate, "owner",
                      FunctionTemplate::New(isolate, RealmOwner));
  realm_template->Set(isolate, "global",
                      FunctionTemplate::New(isolate, RealmGlobal));
  realm_template->Set(isolate, "create",
                      FunctionTemplate::New(isolate, RealmCreate));
  realm_template->Set(
      isolate, "createAllowCrossRealmAccess",
      FunctionTemplate::New(isolate, RealmCreateAllowCrossRealmAccess));
  realm_template->Set(isolate, "navigate",
                      FunctionTemplate::New(isolate, RealmNavigate));
  realm_template->Set(isolate, "navigateSameOrigin",
                      FunctionTemplate::New(isolate, RealmNavigateSameOrigin));
  realm_template->Set(isolate, "detachGlobal",
                      FunctionTemplate::New(isolate, RealmDetachGlobal));
  realm_template->Set(isolate, "dispose",
                      FunctionTemplate::New(isolate, RealmDispose));
  realm_template->Set(isolate, "switch",
                      FunctionTemplate::New(isolate, RealmSwitch));
  realm_template->Set(isolate, "eval",
                      FunctionTemplate::New(isolate, RealmEval));
  realm_template->SetNativeDataProperty(
      String::NewFromUtf8Literal(isolate, "shared"), RealmSharedGet,
      RealmSharedSet);
  return realm_template;
}

Local<ObjectTemplate> Shell::CreateD8Template(Isolate* isolate) {
  Local<ObjectTemplate> d8_template = ObjectTemplate::New(isolate);
  {
    Local<ObjectTemplate> file_template = ObjectTemplate::New(isolate);
    file_template->Set(isolate, "read",
                       FunctionTemplate::New(isolate, Shell::ReadFile));
    file_template->Set(isolate, "execute",
                       FunctionTemplate::New(isolate, Shell::ExecuteFile));
#if V8_TARGET_OS_LINUX && V8_ENABLE_WEBASSEMBLY
    if (i::v8_flags.experimental_wasm_memory_control) {
      file_template->Set(
          isolate, "create_wasm_memory_map_descriptor",
          FunctionTemplate::New(isolate, Shell::CreateWasmMemoryMapDescriptor));
    }
#endif
    d8_template->Set(isolate, "file", file_template);
  }
  {
    Local<ObjectTemplate> log_template = ObjectTemplate::New(isolate);
    log_template->Set(isolate, "getAndStop",
                      FunctionTemplate::New(isolate, LogGetAndStop));

    d8_template->Set(isolate, "log", log_template);
  }
  {
    Local<ObjectTemplate> dom_template = ObjectTemplate::New(isolate);
    Local<FunctionTemplate> event_target =
        Shell::CreateEventTargetTemplate(isolate);
    dom_template->Set(isolate, "EventTarget", event_target);
    dom_template->Set(isolate, "Div",
                      Shell::CreateNodeTemplates(isolate, event_target));
    d8_template->Set(isolate, "dom", dom_template);
  }
  {
    Local<ObjectTemplate> test_template = ObjectTemplate::New(isolate);
    // For different runs of correctness fuzzing the bytecode of a function
    // might get flushed, resulting in spurious errors.
    if (!i::v8_flags.correctness_fuzzer_suppressions) {
      test_template->Set(
          isolate, "verifySourcePositions",
          FunctionTemplate::New(isolate, TestVerifySourcePositions));
    }
    // Correctness fuzzing will attempt to compare results of tests with and
    // without turbo_fast_api_calls, so we don't expose the fast_c_api
    // constructor when --correctness_fuzzer_suppressions is on.
    if (options.expose_fast_api && i::v8_flags.turbo_fast_api_calls &&
        !i::v8_flags.correctness_fuzzer_suppressions) {
      test_template->Set(isolate, "FastCAPI",
                         Shell::CreateTestFastCApiTemplate(isolate));
      test_template->Set(isolate, "LeafInterfaceType",
                         Shell::CreateLeafInterfaceTypeTemplate(isolate));
    }
    // Allows testing code paths that are triggered when Origin Trials are
    // added in the browser.
    test_template->Set(
        isolate, "installConditionalFeatures",
        FunctionTemplate::New(isolate, Shell::InstallConditionalFeatures));

    test_template->Set(
        isolate, "setFlushDenormals",
        FunctionTemplate::New(isolate, Shell::SetFlushDenormals));

    d8_template->Set(isolate, "test", test_template);
  }
  {
    Local<ObjectTemplate> promise_template = ObjectTemplate::New(isolate);
    promise_template->Set(
        isolate, "setHooks",
        FunctionTemplate::New(isolate, SetPromiseHooks, Local<Value>(),
                              Local<Signature>(), 4));
    d8_template->Set(isolate, "promise", promise_template);
  }
  {
    Local<ObjectTemplate> debugger_template = ObjectTemplate::New(isolate);
    debugger_template->Set(
        isolate, "enable",
        FunctionTemplate::New(isolate, EnableDebugger, Local<Value>(),
                              Local<Signature>(), 0));
    debugger_template->Set(
        isolate, "disable",
        FunctionTemplate::New(isolate, DisableDebugger, Local<Value>(),
                              Local<Signature>(), 0));
    d8_template->Set(isolate, "debugger", debugger_template);
  }
  {
    Local<ObjectTemplate> serializer_template = ObjectTemplate::New(isolate);
    serializer_template->Set(
        isolate, "serialize",
        FunctionTemplate::New(isolate, SerializerSerialize));
    serializer_template->Set(
        isolate, "deserialize",
        FunctionTemplate::New(isolate, SerializerDeserialize, Local<Value>(),
                              Local<Signature>(), 1));
    d8_template->Set(isolate, "serializer", serializer_template);
  }
#if V8_ENABLE_WEBASSEMBLY
  {
    Local<ObjectTemplate> wasm_template = ObjectTemplate::New(isolate);
    wasm_template->Set(isolate, "serializeModule",
                       FunctionTemplate::New(isolate, WasmSerializeModule));
    wasm_template->Set(isolate, "deserializeModule",
                       FunctionTemplate::New(isolate, WasmDeserializeModule));
    d8_template->Set(isolate, "wasm", wasm_template);
  }
#endif  // V8_ENABLE_WEBASSEMBLY
  {
    Local<ObjectTemplate> profiler_template = ObjectTemplate::New(isolate);
    profiler_template->Set(
        isolate, "setOnProfileEndListener",
        FunctionTemplate::New(isolate, ProfilerSetOnProfileEndListener));
    profiler_template->Set(
        isolate, "triggerSample",
        FunctionTemplate::New(isolate, ProfilerTriggerSample));
    d8_template->Set(isolate, "profiler", profiler_template);
  }
  {
    Local<ObjectTemplate> constants_template = ObjectTemplate::New(isolate);
    if (!i::v8_flags.correctness_fuzzer_suppressions) {
      // Don't expose these constants in differential-fuzzing builds as they
      // differ with lower-limits mode.
      constants_template->Set(
          String::NewFromUtf8Literal(isolate, "maxFixedArrayCapacity",
                                     NewStringType::kInternalized),
          Number::New(isolate, i::kMaxFixedArrayCapacity));
      constants_template->Set(
          String::NewFromUtf8Literal(isolate, "maxFastArrayLength",
                                     NewStringType::kInternalized),
          Number::New(isolate, i::JSArray::kMaxFastArrayLength));
    }
    d8_template->Set(isolate, "constants", constants_template);
  }
#ifdef V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA
  d8_template->Set(
      isolate, "getContinuationPreservedEmbedderDataViaAPIForTesting",
      FunctionTemplate::New(isolate, GetContinuationPreservedEmbedderData));
#endif  // V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA
  d8_template->Set(isolate, "terminateNow",
                   FunctionTemplate::New(isolate, TerminateNow));
  d8_template->Set(isolate, "terminate",
                   FunctionTemplate::New(isolate, ScheduleTermination));
  d8_template->Set(isolate, "getExtrasBindingObject",
                   FunctionTemplate::New(isolate, GetExtrasBindingObject));
  if (!options.omit_quit) {
    d8_template->Set(isolate, "quit", FunctionTemplate::New(isolate, Quit));
  }
  return d8_template;
}

static void PrintMessageCallback(Local<Message> message, Local<Value> error) {
  Isolate* isolate = Isolate::GetCurrent();
  switch (message->ErrorLevel()) {
    case v8::Isolate::kMessageWarning:
    case v8::Isolate::kMessageLog:
    case v8::Isolate::kMessageInfo:
    case v8::Isolate::kMessageDebug: {
      break;
    }

    case v8::Isolate::kMessageError: {
      Shell::ReportException(isolate, message, error);
      return;
    }

    default: {
      UNREACHABLE();
    }
  }
  // Converts a V8 value to a C string.
  auto ToCString = [](const v8::String::Utf8Value& value) {
    return *value ? *value : "<string conversion failed>";
  };
  v8::String::Utf8Value msg(isolate, message->Get());
  const char* msg_string = ToCString(msg);
  // Print (filename):(line number): (message).
  v8::String::Utf8Value filename(isolate,
                                 message->GetScriptOrigin().ResourceName());
  const char* filename_string = ToCString(filename);
  Maybe<int> maybeline = message->GetLineNumber(isolate->GetCurrentContext());
  int linenum = maybeline.IsJust() ? maybeline.FromJust() : -1;
  printf("%s:%i: %s\n", filename_string, linenum, msg_string);
}

void Shell::PromiseRejectCallback(v8::PromiseRejectMessage data) {
  if (options.ignore_unhandled_promises) return;
  if (data.GetEvent() == v8::kPromiseRejectAfterResolved ||
      data.GetEvent() == v8::kPromiseResolveAfterResolved) {
    // Ignore reject/resolve after resolved.
    return;
  }
  v8::Local<v8::Promise> promise = data.GetPromise();
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  PerIsolateData* isolate_data = PerIsolateData::Get(isolate);

  if (data.GetEvent() == v8::kPromiseHandlerAddedAfterReject) {
    isolate_data->RemoveUnhandledPromise(promise);
    return;
  }

  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  bool capture_exceptions =
      i_isolate->get_capture_stack_trace_for_uncaught_exceptions();
  isolate->SetCaptureStackTraceForUncaughtExceptions(true);
  v8::Local<Value> exception = data.GetValue();
  v8::Local<Message> message;
  // Assume that all objects are stack-traces.
  if (exception->IsObject()) {
    message = v8::Exception::CreateMessage(isolate, exception);
  }
  if (!exception->IsNativeError() &&
      (message.IsEmpty() || message->GetStackTrace().IsEmpty())) {
    // If there is no real Error object, manually create a stack trace.
    exception = v8::Exception::Error(
        v8::String::NewFromUtf8Literal(isolate, "Unhandled Promise."));
    message = Exception::CreateMessage(isolate, exception);
  }
  isolate->SetCaptureStackTraceForUncaughtExceptions(capture_exceptions);

  isolate_data->AddUnhandledPromise(promise, message, exception);
}

void Shell::InitializeMainThreadCounters(Isolate* isolate) {
  {
    base::MutexGuard mutex_guard(&counter_mutex_);
    counter_map_->clear();
  }
  if (i::v8_flags.map_counters[0] != '\0') {
    MapCounters(isolate, i::v8_flags.map_counters);
  }
  InitializeDefaultCounters(isolate);
}

void Shell::InitializeDefaultCounters(Isolate* v8_isolate) {
  if (!options.dump_counters && !options.dump_counters_nvp) return;
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
  auto counters = i_isolate->counters();
  // Force-initialize common counters that are used in our benchmarks
  // and thus should always be printed if --dump-counters is used.
  counters->deopts()->Get();
  counters->maps_created()->Get();
}

void Shell::Initialize(Isolate* isolate, D8Console* console,
                       bool isOnMainThread) {
  if (!options.can_block) isolate->SetAllowAtomicsWait(false);

  isolate->SetPromiseRejectCallback(PromiseRejectCallback);
  isolate->SetWasmAsyncResolvePromiseCallback(
      D8WasmAsyncResolvePromiseCallback);
  if (isOnMainThread) {
    InitializeMainThreadCounters(isolate);

    // Disable default message reporting.
    isolate->AddMessageListenerWithErrorLevel(
        PrintMessageCallback,
        v8::Isolate::kMessageError | v8::Isolate::kMessageWarning |
            v8::Isolate::kMessageInfo | v8::Isolate::kMessageDebug |
            v8::Isolate::kMessageLog);
  }

  isolate->SetHostImportModuleDynamicallyCallback(
      Shell::HostImportModuleDynamically);
  isolate->SetHostImportModuleWithPhaseDynamicallyCallback(
      Shell::HostImportModuleWithPhaseDynamically);
  isolate->SetHostInitializeImportMetaObjectCallback(
      Shell::HostInitializeImportMetaObject);
  isolate->SetHostCreateShadowRealmContextCallback(
      Shell::HostCreateShadowRealmContext);

  debug::SetConsoleDelegate(isolate, console);
}

Local<String> Shell::WasmLoadSourceMapCallback(Isolate* isolate,
                                               const char* path) {
  return Shell::ReadFile(isolate, path, false).ToLocalChecked();
}

MaybeLocal<Context> Shell::CreateEvaluationContext(Isolate* isolate) {
  // This needs to be a critical section since this is not thread-safe
  i::ParkedMutexGuard lock_guard(
      reinterpret_cast<i::Isolate*>(isolate)->main_thread_local_isolate(),
      context_mutex_.Pointer());
  // Initialize the global objects
  Local<ObjectTemplate> global_template = CreateGlobalTemplate(isolate);
  EscapableHandleScope handle_scope(isolate);
  Local<Context> context = Context::New(isolate, nullptr, global_template);
  if (context.IsEmpty()) {
    DCHECK(isolate->IsExecutionTerminating());
    return {};
  }
  if (i::v8_flags.perf_prof_annotate_wasm ||
      i::v8_flags.vtune_prof_annotate_wasm) {
    isolate->SetWasmLoadSourceMapCallback(Shell::WasmLoadSourceMapCallback);
  }
  InitializeModuleEmbedderData(context);
  Context::Scope scope(context);
  if (options.include_arguments) {
    const std::vector<const char*>& args = options.arguments;
    int size = static_cast<int>(args.size());
    Local<Array> array = Array::New(isolate, size);
    for (int i = 0; i < size; i++) {
      Local<String> arg =
          v8::String::NewFromUtf8(isolate, args[i]).ToLocalChecked();
      Local<Number> index = v8::Number::New(isolate, i);
      array->Set(context, index, arg).FromJust();
    }
    Local<String> name = String::NewFromUtf8Literal(
        isolate, "arguments", NewStringType::kInternalized);
    context->Global()->Set(context, name, array).FromJust();
  }
  {
    // setup console global.
    Local<String> name = String::NewFromUtf8Literal(
        isolate, "console", NewStringType::kInternalized);
    Local<Value> console =
        context->GetExtrasBindingObject()->Get(context, name).ToLocalChecked();
    context->Global()->Set(context, name, console).FromJust();
  }
#ifdef V8_ENABLE_HARDWARE_WATCHPOINT_SUPPORT
  if (options.memory_corruption_via_watchpoints) {
    // The global "Sandbox" object is installed in src/sandbox/testing.cc via
    // `SandboxTesting::InstallMemoryCorruptionApi`. We add another method to
    // it with a callback implemented in hardware-watchpoints.cc.
    Local<Object> sandbox_object =
        context->Global()
            ->Get(context,
                  String::NewFromUtf8Literal(isolate, "Sandbox",
                                             NewStringType::kInternalized))
            .ToLocalChecked()
            .As<Object>();
    Local<FunctionTemplate> templ = FunctionTemplate::New(
        isolate, SetHardwareWatchpointCallback, {}, {}, 0,
        ConstructorBehavior::kThrow, SideEffectType::kHasSideEffect);
    Local<Function> function = templ->GetFunction(context).ToLocalChecked();
    CHECK(sandbox_object
              ->Set(context,
                    String::NewFromUtf8Literal(isolate,
                                               "markForCorruptionOnAccess",
                                               NewStringType::kInternalized),
                    function)
              .FromMaybe(false));
  }
#endif  // V8_ENABLE_HARDWARE_WATCHPOINT_SUPPORT

  return handle_scope.Escape(context);
}

void Shell::WriteIgnitionDispatchCountersFile(v8::Isolate* isolate) {
  HandleScope handle_scope(isolate);
  Local<Context> context = Context::New(isolate);
  Context::Scope context_scope(context);

  i::DirectHandle<i::JSObject> dispatch_counters =
      reinterpret_cast<i::Isolate*>(isolate)
          ->interpreter()
          ->GetDispatchCountersObject();
  std::ofstream dispatch_counters_stream(
      i::v8_flags.trace_ignition_dispatches_output_file);
  dispatch_counters_stream << *String::Utf8Value(
      isolate, JSON::Stringify(context, Utils::ToLocal(dispatch_counters))
                   .ToLocalChecked());
}

namespace {
int LineFromOffset(Local<debug::Script> script, int offset) {
  debug::Location location = script->GetSourceLocation(offset);
  return location.GetLineNumber();
}

void WriteLcovDataForRange(std::vector<uint32_t>* lines, int start_line,
                           int end_line, uint32_t count) {
  // Ensure space in the array.
  lines->resize(std::max(static_cast<size_t>(end_line + 1), lines->size()), 0);
  // Boundary lines could be shared between two functions with different
  // invocation counts. Take the maximum.
  (*lines)[start_line] = std::max((*lines)[start_line], count);
  (*lines)[end_line] = std::max((*lines)[end_line], count);
  // Invocation counts for non-boundary lines are overwritten.
  for (int k = start_line + 1; k < end_line; k++) (*lines)[k] = count;
}

void WriteLcovDataForNamedRange(std::ostream& sink,
                                std::vector<uint32_t>* lines,
                                const std::string& name, int start_line,
                                int end_line, uint32_t count) {
  WriteLcovDataForRange(lines, start_line, end_line, count);
  sink << "FN:" << start_line + 1 << "," << name << std::endl;
  sink << "FNDA:" << count << "," << name << std::endl;
}
}  // namespace

#if V8_ENABLE_WEBASSEMBLY
void WriteWasmLcovData(v8::Isolate* isolate, const char* file) {
  HandleScope handle_scope(isolate);
  debug::Coverage coverage = debug::Coverage::CollectWasmData(isolate);

#if defined(V8_OS_WIN)
  constexpr size_t kMaxPath = MAX_PATH;
#else
  constexpr size_t kMaxPath = PATH_MAX;
#endif

  std::string normalized_js_lcov_file =
      NormalizePath(file, GetWorkingDirectory());
  std::string folder_path = DirName(normalized_js_lcov_file);
  std::string wasm_lcov_file =
      RemoveExtension(normalized_js_lcov_file) + "-wasm.lcov";
  if (wasm_lcov_file.length() >= kMaxPath) {
    fprintf(stderr, "Invalid wasm-lcov filename '%s', the path is too long.\n",
            wasm_lcov_file.c_str());
    return;
  }
  std::ofstream sink(wasm_lcov_file.c_str(), std::ofstream::app);

  for (size_t i_script = 0; i_script < coverage.ScriptCount(); i_script++) {
    debug::Coverage::ScriptData script_data = coverage.GetScriptData(i_script);
    Local<debug::Script> script = script_data.GetScript();
    auto wasm_script = Utils::OpenDirectHandle(*script);
    i::wasm::NativeModule* native_module = wasm_script->wasm_native_module();
    const i::wasm::WasmModule* wasm_module = native_module->module();

    constexpr int kMaxDisasmFileNameSize = 33;
    char disasm_file_name[kMaxDisasmFileNameSize];
    snprintf(disasm_file_name, kMaxDisasmFileNameSize, "wasm-%zu.disasm",
             i_script);
    std::string disasm_file_path = folder_path + "/" + disasm_file_name;
    if (disasm_file_path.length() >= kMaxPath) {
      fprintf(stderr,
              "Invalid wasm disassembly filename '%s', the path is too long.\n",
              disasm_file_path.c_str());
      return;
    }
    std::ofstream disasm_file(disasm_file_path.c_str());

    std::vector<int> function_body_offsets;
    std::map<uint32_t, uint32_t> bytecode_disasm_offsets;
    uint32_t lines_count = native_module->DisassembleForLcov(
        disasm_file, function_body_offsets, bytecode_disasm_offsets);

    std::vector<uint32_t> lines;
    lines.resize(lines_count);

    sink << "SF:";
    sink << disasm_file_path << std::endl;

    DCHECK_EQ(script_data.FunctionCount(), wasm_module->num_declared_functions);

    for (size_t declared_function_index = 0;
         declared_function_index < wasm_module->num_declared_functions;
         declared_function_index++) {
      debug::Coverage::FunctionData function_data =
          script_data.GetFunctionData(declared_function_index);
      const i::wasm::WasmFunction& function =
          wasm_module->functions[wasm_module->num_imported_functions +
                                 declared_function_index];

      // Write function stats.
      {
        // {function_body_offsets} contains 2 * num_defined_functions entries,
        // for each not imported function it contains the bytecode offset of the
        // first instruction in the function, followed by the end offset.
        int start_line = bytecode_disasm_offsets[function.code.offset()];
        uint32_t first_instruction_offset =
            function_body_offsets[declared_function_index * 2];
        int first_instruction_line =
            bytecode_disasm_offsets[first_instruction_offset];
        uint32_t block_count = function_data.BlockCount() == 0
                                   ? 0
                                   : function_data.GetBlockData(0).Count();

        Local<String> function_name;
        std::stringstream name_stream;
        if (function_data.Name().ToLocal(&function_name)) {
          name_stream << ToSTLString(isolate, function_name);
        } else {
          name_stream << "<" << start_line << ">";
        }

        // Function header.
        WriteLcovDataForNamedRange(sink, &lines, name_stream.str(), start_line,
                                   first_instruction_line, block_count);
      }

      // Process inner blocks.
      for (size_t i_block = 0; i_block < function_data.BlockCount();
           i_block++) {
        debug::Coverage::BlockData block_data =
            function_data.GetBlockData(i_block);
        int start_offset = block_data.StartOffset() + function.code.offset();
        auto it = bytecode_disasm_offsets.upper_bound(start_offset);
        DCHECK_NE(it, bytecode_disasm_offsets.begin());
        int start_line = (--it)->second;
        int end_offset = block_data.EndOffset() + function.code.offset();
        it = bytecode_disasm_offsets.upper_bound(end_offset);
        DCHECK_NE(it, bytecode_disasm_offsets.begin());
        int end_line = (--it)->second;
        WriteLcovDataForRange(&lines, start_line, end_line, block_data.Count());
      }
    }
    // Write per-line coverage. LCOV uses 1-based line numbers.
    for (size_t j = 0; j < lines.size(); j++) {
      sink << "DA:" << (j + 1) << "," << lines[j] << std::endl;
    }
    sink << "end_of_record" << std::endl;
  }
  printf("Wrote WebAssembly LCOV data to %s\n", wasm_lcov_file.c_str());
}
#endif  // V8_ENABLE_WEBASSEMBLY

// Write coverage data in LCOV format. See man page for geninfo(1).
void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) {
  if (!file) return;

#if V8_ENABLE_WEBASSEMBLY
  if (i::v8_flags.wasm_code_coverage) {
    WriteWasmLcovData(isolate, file);
  }
#endif  // V8_ENABLE_WEBASSEMBLY

  HandleScope handle_scope(isolate);
  debug::Coverage coverage = debug::Coverage::CollectPrecise(isolate);
  std::ofstream sink(file, std::ofstream::app);
  for (size_t i = 0; i < coverage.ScriptCount(); i++) {
    debug::Coverage::ScriptData script_data = coverage.GetScriptData(i);
    Local<debug::Script> script = script_data.GetScript();
    // Skip unnamed scripts.
    Local<String> name;
    if (!script->Name().ToLocal(&name)) continue;
    std::string file_name = ToSTLString(isolate, name);
    // Skip scripts not backed by a file.
    if (!std::ifstream(file_name).good()) continue;
    sink << "SF:";
    sink << NormalizePath(file_name, GetWorkingDirectory()) << std::endl;
    std::vector<uint32_t> lines;
    for (size_t j = 0; j < script_data.FunctionCount(); j++) {
      debug::Coverage::FunctionData function_data =
          script_data.GetFunctionData(j);

      // Write function stats.
      {
        debug::Location start =
            script->GetSourceLocation(function_data.StartOffset());
        debug::Location end =
            script->GetSourceLocation(function_data.EndOffset());
        int start_line = start.GetLineNumber();
        int end_line = end.GetLineNumber();
        uint32_t count = function_data.Count();

        Local<String> function_name;
        std::stringstream name_stream;
        if (function_data.Name().ToLocal(&function_name)) {
          name_stream << ToSTLString(isolate, function_name);
        } else {
          name_stream << "<" << start_line + 1 << "-";
          name_stream << start.GetColumnNumber() << ">";
        }

        WriteLcovDataForNamedRange(sink, &lines, name_stream.str(), start_line,
                                   end_line, count);
      }

      // Process inner blocks.
      for (size_t k = 0; k < function_data.BlockCount(); k++) {
        debug::Coverage::BlockData block_data = function_data.GetBlockData(k);
        int start_line = LineFromOffset(script, block_data.StartOffset());
        int end_line = LineFromOffset(script, block_data.EndOffset() - 1);
        uint32_t count = block_data.Count();
        WriteLcovDataForRange(&lines, start_line, end_line, count);
      }
    }
    // Write per-line coverage. LCOV uses 1-based line numbers.
    for (size_t j = 0; j < lines.size(); j++) {
      sink << "DA:" << (j + 1) << "," << lines[j] << std::endl;
    }
    sink << "end_of_record" << std::endl;
  }
}

void Shell::OnExit(v8::Isolate* isolate, bool dispose) {
#ifdef V8_ENABLE_HARDWARE_WATCHPOINT_SUPPORT
  if (v8::Shell::options.memory_corruption_via_watchpoints) {
    ResetAllHardwareWatchpoints();
  }
#endif  // V8_ENABLE_HARDWARE_WATCHPOINT_SUPPORT

  platform::NotifyIsolateShutdown(g_default_platform, isolate);

  if (Worker* worker = Worker::GetCurrentWorker()) {
    // When invoking `quit` on a worker isolate, the worker needs to reach
    // State::kTerminated before invoking Isolate::Dispose. This is because the
    // main thread tries to terminate all workers at the end, which can happen
    // concurrently to Isolate::Dispose.
    worker->EnterTerminatedState();
  }

  if (dispose) {
    isolate->Dispose();
  } else {
    // Normally, Dispose() prints counters. Benchmarks expect counters to be
    // printed on process exit, so do so manually if not disposing.
    isolate->DumpAndResetStats();
  }

  // Simulate errors before disposing V8, as that resets flags (via
  // FlagList::ResetAllFlags()), but error simulation reads the random seed.
  if (options.simulate_errors && is_valid_fuzz_script()) {
    // Simulate several errors detectable by fuzzers behind a flag if the
    // minimum file size for fuzzing was executed.
    FuzzerMonitor::SimulateErrors();
  }

  if (dispose) {
    V8::Dispose();
    V8::DisposePlatform();
  }

  if (options.dump_counters || options.dump_counters_nvp) {
    DumpCounters();
  }

  if (options.dump_system_memory_stats) {
    int peak_memory_usage = base::OS::GetPeakMemoryUsageKb();
    std::cout << "System peak memory usage (kb): " << peak_memory_usage
              << std::endl;
    // TODO(jdapena): call rusage platform independent call, and extract peak
    // memory usage to print it
  }

  // Only delete the counters if we are done executing; after calling `quit`,
  // other isolates might still be running and accessing that memory. This is a
  // memory leak, which is OK in this case.
  if (dispose) {
    delete counters_file_;
    delete counter_map_;
  }
}

void Shell::DumpCounters() {
  base::MutexGuard mutex_guard(&counter_mutex_);
  std::vector<std::pair<std::string, Counter*>> counters(counter_map_->begin(),
                                                         counter_map_->end());
  std::sort(counters.begin(), counters.end());

  if (options.dump_counters_nvp) {
    // Dump counters as name-value pairs.
    for (const auto& pair : counters) {
      std::string key = pair.first;
      Counter* counter = pair.second;
      if (counter->is_histogram()) {
        std::cout << "\"c:" << key << "\"=" << counter->count() << "\n";
        std::cout << "\"t:" << key << "\"=" << counter->sample_total() << "\n";
      } else {
        std::cout << "\"" << key << "\"=" << counter->count() << "\n";
      }
    }
    return;
  }

  // Dump counters in formatted boxes.
  constexpr int kNameBoxSize = 64;
  constexpr int kValueBoxSize = 13;
  std::cout << std::string(kNameBoxSize, '-') << "+"
            << std::string(kValueBoxSize - 1, '-') << "\n";
  std::cout << "Name" << std::string(kNameBoxSize - 4, ' ') << "| Value"
            << std::string(kValueBoxSize - 7, ' ') << "\n";
  std::cout << std::string(kNameBoxSize, '-') << "+"
            << std::string(kValueBoxSize - 1, '-') << "\n";
  for (const auto& pair : counters) {
    std::string key = pair.first;
    Counter* counter = pair.second;
    if (counter->is_histogram()) {
      std::cout << "c:" << std::setw(kNameBoxSize - 2) << std::left << key
                << std::setw(kValueBoxSize) << std::right << counter->count()
                << "\n";
      std::cout << "t:" << std::setw(kNameBoxSize - 2) << std::left << key
                << std::setw(kValueBoxSize) << std::right
                << counter->sample_total() << "\n";
    } else {
      std::cout << std::setw(kNameBoxSize) << std::left << key
                << std::setw(kValueBoxSize) << std::right << counter->count()
                << "\n";
    }
  }
}

void Dummy(char* arg) {}

V8_NOINLINE void FuzzerMonitor::SimulateErrors() {
  // Initialize a fresh RNG to not interfere with JS execution.
  std::unique_ptr<base::RandomNumberGenerator> rng;
  int64_t seed = i::v8_flags.random_seed;
  if (seed != 0) {
    rng = std::make_unique<base::RandomNumberGenerator>(seed);
  } else {
    rng = std::make_unique<base::RandomNumberGenerator>();
  }

  double p = rng->NextDouble();
  if (p < 0.1) {
    ControlFlowViolation();
  } else if (p < 0.2) {
    DCheck();
  } else if (p < 0.3) {
    Fatal();
  } else if (p < 0.4) {
    ObservableDifference();
  } else if (p < 0.5) {
    UndefinedBehavior();
  } else if (p < 0.6) {
    UseAfterFree();
  } else if (p < 0.7) {
    UseOfUninitializedValue();
  }
}

V8_NOINLINE void FuzzerMonitor::ControlFlowViolation() {
  // Control flow violation caught by CFI.
  void (*func)() = (void (*)()) & Dummy;
  func();
}

V8_NOINLINE void FuzzerMonitor::DCheck() {
  // Caught in debug builds.
  DCHECK(false);
}

V8_NOINLINE void FuzzerMonitor::Fatal() {
  // Caught in all build types.
  FATAL("Fake error.");
}

V8_NOINLINE void FuzzerMonitor::ObservableDifference() {
  // Observable difference caught by differential fuzzing.
  printf("___fake_difference___\n");
}

V8_NOINLINE void FuzzerMonitor::UndefinedBehavior() {
  // Caught by UBSAN.
  int32_t val = -1;
  USE(val << val);
}

V8_NOINLINE void FuzzerMonitor::UseAfterFree() {
  // Use-after-free caught by ASAN.
#if defined(__clang__)  // GCC-12 detects this at compile time!
  std::vector<bool>* storage = new std::vector<bool>(3);
  delete storage;
  USE(storage->at(1));
#endif
}

V8_NOINLINE void FuzzerMonitor::UseOfUninitializedValue() {
// Use-of-uninitialized-value caught by MSAN.
#if defined(__clang__)
  int uninitialized[1];
  if (uninitialized[0]) USE(uninitialized);
#endif
}

char* Shell::ReadChars(const char* name, int* size_out) {
  if (options.read_from_tcp_port >= 0) {
    return ReadCharsFromTcpPort(name, size_out);
  }

  FILE* file = base::OS::FOpen(name, "rb");
  if (file == nullptr) return nullptr;

  fseek(file, 0, SEEK_END);
  size_t size = ftell(file);
  rewind(file);

  char* chars = new char[size + 1];
  chars[size] = '\0';
  for (size_t i = 0; i < size;) {
    i += fread(&chars[i], 1, size - i, file);
    if (ferror(file)) {
      base::Fclose(file);
      delete[] chars;
      return nullptr;
    }
  }
  base::Fclose(file);
  *size_out = base::checked_cast<int>(size);
  return chars;
}

MaybeLocal<PrimitiveArray> Shell::ReadLines(Isolate* isolate,
                                            const char* name) {
  int length;
  std::unique_ptr<char[]> data(ReadChars(name, &length));

  if (data.get() == nullptr) {
    return MaybeLocal<PrimitiveArray>();
  }
  std::stringstream stream(data.get());
  std::string line;
  std::vector<std::string> lines;
  while (std::getline(stream, line, '\n')) {
    lines.emplace_back(line);
  }
  // Create a Local<PrimitiveArray> off the read lines.
  int size = base::checked_cast<int>(lines.size());
  Local<PrimitiveArray> exports = PrimitiveArray::New(isolate, size);
  for (int i = 0; i < size; ++i) {
    MaybeLocal<String> maybe_str = v8::String::NewFromUtf8(
        isolate, lines[i].c_str(), NewStringType::kNormal,
        static_cast<int>(lines[i].length()));
    Local<String> str;
    if (!maybe_str.ToLocal(&str)) {
      return MaybeLocal<PrimitiveArray>();
    }
    exports->Set(isolate, i, str);
  }
  return exports;
}

void Shell::ReadBuffer(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  static_assert(sizeof(char) == sizeof(uint8_t),
                "char and uint8_t should both have 1 byte");
  Isolate* isolate = info.GetIsolate();
  String::Utf8Value filename(isolate, info[0]);
  int length;
  if (*filename == nullptr) {
    ThrowError(isolate, "Error loading file");
    return;
  }

  uint8_t* data = reinterpret_cast<uint8_t*>(ReadChars(*filename, &length));
  if (data == nullptr) {
    std::ostringstream error_msg;
    error_msg << "Error reading file \""
              << std::string_view{*filename,
                                  std::min(size_t{50}, filename.length())}
              << (filename.length() > 50 ? "[...]" : "") << "\"";
    ThrowError(isolate, error_msg.str());
    return;
  }
  Local<v8::ArrayBuffer> buffer = ArrayBuffer::New(isolate, length);
  memcpy(buffer->GetBackingStore()->Data(), data, length);
  delete[] data;

  info.GetReturnValue().Set(buffer);
}

void Shell::ReadLine(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Local<v8::String> input;
  if (!ReadFromStdin(info.GetIsolate()).ToLocal(&input)) {
    // In case of an error or EOF, the empty handle will set the default return
    // value.
    CHECK(input.IsEmpty());
  }
  info.GetReturnValue().Set(input);
}

// Reads a file into a memory blob.
std::unique_ptr<base::OS::MemoryMappedFile> Shell::ReadFileData(
    Isolate* isolate, const char* name, bool should_throw) {
  std::unique_ptr<base::OS::MemoryMappedFile> file(
      base::OS::MemoryMappedFile::open(
          name, base::OS::MemoryMappedFile::FileMode::kReadOnly));
  if (!file) {
    if (should_throw) {
      std::ostringstream oss;
      oss << "Error loading file: " << name;
      ThrowError(isolate, oss.view());
    }
    return nullptr;
  }
  return file;
}

// Reads a file into a v8 string.
MaybeLocal<String> Shell::ReadFile(Isolate* isolate, const char* name,
                                   bool should_throw) {
  auto file = ReadFileData(isolate, name, should_throw);
  if (!file) {
    return MaybeLocal<String>();
  }
  size_t full_file_size = file->size();
  if (full_file_size > size_t{String::kMaxLength}) {
    FATAL("Input file too large (%zu bytes)", full_file_size);
  }
  static_assert(String::kMaxLength <= i::kMaxInt);
  int size = static_cast<int>(full_file_size);
  char* chars = static_cast<char*>(file->memory());
  if (i::v8_flags.use_external_strings && i::String::IsAscii(chars, size)) {
    String::ExternalOneByteStringResource* resource =
        new i::OwningExternalOneByteStringResource(
            std::string_view(chars, size));
    return String::NewExternalOneByte(isolate, resource);
  }
  return String::NewFromUtf8(isolate, chars, NewStringType::kNormal, size);
}

void Shell::WriteChars(const char* name, uint8_t* buffer, size_t buffer_size) {
  FILE* file = base::Fopen(name, "w");
  if (file == nullptr) return;
  fwrite(buffer, 1, buffer_size, file);
  base::Fclose(file);
}

void Shell::RunShell(Isolate* isolate) {
  Global<Context> context;
  {
    HandleScope scope(isolate);
    context.Reset(isolate, Local<Context>::New(isolate, evaluation_context_));
  }
  PerIsolateData::RealmScope realm_scope(isolate, context);
  printf("V8 version %s\n", V8::GetVersion());
  while (true) {
    Global<Value> global_result;
    bool success;
    {
      HandleScope scope(isolate);
      Context::Scope context_scope(context.Get(isolate));
      printf("d8> ");
      Local<String> input;
      if (!Shell::ReadFromStdin(isolate).ToLocal(&input)) break;
      Local<String> name = String::NewFromUtf8Literal(isolate, "(d8)");
      success = ExecuteString(isolate, input, name, kReportExceptions,
                              &global_result);
      CHECK_EQ(success, !global_result.IsEmpty());
    }
    if (!FinishExecuting(isolate, context)) success = false;
    if (success) {
      HandleScope scope(isolate);
      Context::Scope context_scope(context.Get(isolate));
      Local<Value> result = global_result.Get(isolate);
      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");
      }
    }
  }
  printf("\n");
}

class InspectorFrontend final : public v8_inspector::V8Inspector::Channel {
 public:
  explicit InspectorFrontend(Local<Context> context) {
    isolate_ = Isolate::GetCurrent();
    context_.Reset(isolate_, context);
  }
  ~InspectorFrontend() override = default;

 private:
  void sendResponse(
      int callId,
      std::unique_ptr<v8_inspector::StringBuffer> message) override {
    Send(message->string());
  }
  void sendNotification(
      std::unique_ptr<v8_inspector::StringBuffer> message) override {
    Send(message->string());
  }
  void flushProtocolNotifications() override {}

  void Send(const v8_inspector::StringView& string) {
    v8::Isolate::AllowJavascriptExecutionScope allow_script(isolate_);
    v8::HandleScope handle_scope(isolate_);
    int length = static_cast<int>(string.length());
    DCHECK_LT(length, v8::String::kMaxLength);
    Local<String> message =
        (string.is8Bit()
             ? v8::String::NewFromOneByte(
                   isolate_,
                   reinterpret_cast<const uint8_t*>(string.characters8()),
                   v8::NewStringType::kNormal, length)
             : v8::String::NewFromTwoByte(
                   isolate_,
                   reinterpret_cast<const uint16_t*>(string.characters16()),
                   v8::NewStringType::kNormal, length))
            .ToLocalChecked();
    Local<String> callback_name = v8::String::NewFromUtf8Literal(
        isolate_, "receive", NewStringType::kInternalized);
    Local<Context> context = context_.Get(isolate_);
    Local<Value> callback =
        context->Global()->Get(context, callback_name).ToLocalChecked();
    if (callback->IsFunction()) {
      v8::TryCatch try_catch(isolate_);
      Local<Value> args[] = {message};
      USE(callback.As<Function>()->Call(context, Undefined(isolate_), 1, args));
#ifdef DEBUG
      if (try_catch.HasCaught()) {
        Local<Object> exception = try_catch.Exception().As<Object>();
        Local<String> key = v8::String::NewFromUtf8Literal(
            isolate_, "message", NewStringType::kInternalized);
        Local<String> expected = v8::String::NewFromUtf8Literal(
            isolate_, "Maximum call stack size exceeded");
        Local<Value> value = exception->Get(context, key).ToLocalChecked();
        DCHECK(value->StrictEquals(expected));
      }
#endif
    }
  }

  Isolate* isolate_;
  Global<Context> context_;
};

class InspectorClient : public v8_inspector::V8InspectorClient {
 public:
  InspectorClient(Isolate* isolate, const Global<Context>& global_context,
                  bool connect) {
    if (!connect) return;
    isolate_ = isolate;
    Local<Context> context = global_context.Get(isolate);
    channel_.reset(new InspectorFrontend(context));
    inspector_ = v8_inspector::V8Inspector::create(isolate_, this);
    session_ =
        inspector_->connect(1, channel_.get(), v8_inspector::StringView(),
                            v8_inspector::V8Inspector::kFullyTrusted,
                            v8_inspector::V8Inspector::kNotWaitingForDebugger);
    context->SetAlignedPointerInEmbedderData(kInspectorClientIndex, this,
                                             kInspectorClientTag);
    inspector_->contextCreated(v8_inspector::V8ContextInfo(
        context, kContextGroupId, v8_inspector::StringView()));

    Local<Value> function =
        FunctionTemplate::New(isolate_, SendInspectorMessage)
            ->GetFunction(context)
            .ToLocalChecked();
    Local<String> function_name = String::NewFromUtf8Literal(
        isolate_, "send", NewStringType::kInternalized);
    CHECK(context->Global()->Set(context, function_name, function).FromJust());

    context_.Reset(isolate_, global_context);
  }

  void runMessageLoopOnPause(int contextGroupId) override {
    v8::Isolate::AllowJavascriptExecutionScope allow_script(isolate_);
    v8::HandleScope handle_scope(isolate_);
    Local<String> callback_name = v8::String::NewFromUtf8Literal(
        isolate_, "handleInspectorMessage", NewStringType::kInternalized);
    Local<Context> context = context_.Get(isolate_);
    Local<Value> callback =
        context->Global()->Get(context, callback_name).ToLocalChecked();
    if (!callback->IsFunction()) return;

    // Running the message loop below may trigger the execution of a stackless
    // GC. We need to override the embedder stack state, to force scanning the
    // stack, if this happens.
    i::Heap* heap = reinterpret_cast<i::Isolate*>(isolate_)->heap();
    i::EmbedderStackStateScope stack_scanning_scope(
        heap, i::EmbedderStackStateOrigin::kExplicitInvocation,
        v8::StackState::kMayContainHeapPointers);

    v8::TryCatch try_catch(isolate_);
    try_catch.SetVerbose(true);
    is_paused = true;

    while (is_paused) {
      USE(callback.As<Function>()->Call(context, Undefined(isolate_), 0, {}));
      if (try_catch.HasCaught()) {
        is_paused = false;
      }
    }
  }

  void quitMessageLoopOnPause() override { is_paused = false; }

 private:
  static v8_inspector::V8InspectorSession* GetSession(Local<Context> context) {
    InspectorClient* inspector_client = static_cast<InspectorClient*>(
        context->GetAlignedPointerFromEmbedderData(kInspectorClientIndex,
                                                   kInspectorClientTag));
    return inspector_client->session_.get();
  }

  Local<Context> ensureDefaultContextInGroup(int group_id) override {
    DCHECK(isolate_);
    DCHECK_EQ(kContextGroupId, group_id);
    return context_.Get(isolate_);
  }

  static void SendInspectorMessage(
      const v8::FunctionCallbackInfo<v8::Value>& info) {
    DCHECK(i::ValidateCallbackInfo(info));
    Isolate* isolate = info.GetIsolate();
    v8::HandleScope handle_scope(isolate);
    Local<Context> context = isolate->GetCurrentContext();
    info.GetReturnValue().Set(Undefined(isolate));
    Local<String> message = info[0]->ToString(context).ToLocalChecked();
    v8_inspector::V8InspectorSession* session =
        InspectorClient::GetSession(context);
    uint32_t length = message->Length();
    std::unique_ptr<uint16_t[]> buffer(new uint16_t[length]);
    message->WriteV2(isolate, 0, length, buffer.get());
    v8_inspector::StringView message_view(buffer.get(), length);
    {
      v8::SealHandleScope seal_handle_scope(isolate);
      session->dispatchProtocolMessage(message_view);
    }
    info.GetReturnValue().Set(True(isolate));
  }

  static const int kContextGroupId = 1;

  std::unique_ptr<v8_inspector::V8Inspector> inspector_;
  std::unique_ptr<v8_inspector::V8InspectorSession> session_;
  std::unique_ptr<v8_inspector::V8Inspector::Channel> channel_;
  bool is_paused = false;
  Global<Context> context_;
  Isolate* isolate_;
};

SourceGroup::~SourceGroup() {
  delete thread_;
  thread_ = nullptr;
}

bool ends_with(const char* input, const char* suffix) {
  size_t input_length = strlen(input);
  size_t suffix_length = strlen(suffix);
  if (suffix_length <= input_length) {
    return strcmp(input + input_length - suffix_length, suffix) == 0;
  }
  return false;
}

bool SourceGroup::Execute(Isolate* isolate) {
  bool success = true;
#ifdef V8_FUZZILLI
  if (fuzzilli_reprl) {
    HandleScope handle_scope(isolate);
    Local<String> file_name =
        String::NewFromUtf8(isolate, "fuzzcode.js", NewStringType::kNormal)
            .ToLocalChecked();

    size_t script_size;
    CHECK_EQ(read(REPRL_CRFD, &script_size, 8), 8);
    char* buffer = new char[script_size + 1];
    char* ptr = buffer;
    size_t remaining = script_size;
    while (remaining > 0) {
      ssize_t rv = read(REPRL_DRFD, ptr, remaining);
      CHECK_GE(rv, 0);
      remaining -= rv;
      ptr += rv;
    }
    buffer[script_size] = 0;

    Local<String> source =
        String::NewFromUtf8(isolate, buffer, NewStringType::kNormal)
            .ToLocalChecked();
    delete[] buffer;
    Shell::set_script_executed();
    if (!Shell::ExecuteString(isolate, source, file_name,
                              Shell::kReportExceptions)) {
      return false;
    }
  }
#endif  // V8_FUZZILLI
  for (int i = begin_offset_; i < end_offset_; ++i) {
    const char* arg = argv_[i];
    if (strcmp(arg, "-e") == 0 && i + 1 < end_offset_) {
      // Execute argument given to -e option directly.
      HandleScope handle_scope(isolate);
      Local<String> file_name = String::NewFromUtf8Literal(isolate, "unnamed");
      Local<String> source =
          String::NewFromUtf8(isolate, argv_[i + 1]).ToLocalChecked();
      Shell::set_script_executed();
      if (!Shell::ExecuteString(isolate, source, file_name,
                                Shell::kReportExceptions)) {
        success = false;
        break;
      }
      ++i;
      continue;
    } else if (ends_with(arg, ".mjs")) {
      Shell::set_script_executed();
      if (!Shell::ExecuteModule(isolate, arg)) {
        success = false;
        break;
      }
      continue;
    } else if (strcmp(arg, "--module") == 0 && i + 1 < end_offset_) {
      // Treat the next file as a module.
      arg = argv_[++i];
      Shell::set_script_executed();
      if (!Shell::ExecuteModule(isolate, arg)) {
        success = false;
        break;
      }
      continue;
    } else if (strcmp(arg, "--json") == 0 && i + 1 < end_offset_) {
      // Treat the next file as a JSON file.
      arg = argv_[++i];
      Shell::set_script_executed();
      if (!Shell::LoadJSON(isolate, arg)) {
        success = false;
        break;
      }
      continue;
    } else if (arg[0] == '-') {
      // Ignore other options. They have been parsed already.
      continue;
    }

    // Use all other arguments as names of files to load and run.
    HandleScope handle_scope(isolate);
    Local<String> file_name =
        String::NewFromUtf8(isolate, arg).ToLocalChecked();
    Local<String> source;
    if (!Shell::ReadFile(isolate, arg).ToLocal(&source)) {
      printf("Error reading '%s'\n", arg);
      base::OS::ExitProcess(1);
    }
    Shell::set_script_executed();
    Shell::update_script_size(source->Length());
    if (!Shell::ExecuteString(isolate, source, file_name,
                              Shell::kReportExceptions)) {
      success = false;
      break;
    }
  }
  return success;
}

SourceGroup::IsolateThread::IsolateThread(SourceGroup* group)
    : base::Thread(GetThreadOptions("IsolateThread")), group_(group) {}

void SourceGroup::ExecuteInThread() {
  base::FlushDenormalsScope denormals_scope(Shell::options.flush_denormals);

  Isolate::CreateParams create_params = GetDefaultIsolateCreateParams();
  Isolate* isolate = Isolate::New(create_params);

  {
    Isolate::Scope isolate_scope(isolate);
    D8Console console(isolate);
    Shell::Initialize(isolate, &console, false);
    PerIsolateData data(isolate);

    for (int i = 0; i < Shell::options.stress_runs; ++i) {
      next_semaphore_.ParkedWait(
          reinterpret_cast<i::Isolate*>(isolate)->main_thread_local_isolate());
      {
        Global<Context> global_context;
        HandleScope scope(isolate);
        {
          Local<Context> context;
          if (!Shell::CreateEvaluationContext(isolate).ToLocal(&context)) {
            DCHECK(isolate->IsExecutionTerminating());
            break;
          }
          global_context.Reset(isolate, context);
        }
        PerIsolateData::RealmScope realm_scope(isolate, global_context);
        InspectorClient inspector_client(isolate, global_context,
                                         Shell::options.enable_inspector);
        {
          // We cannot use a Context::Scope here, as it keeps a local handle to
          // the context and SourceGroup::Execute may execute a non-nestable
          // task, e.g. a stackless GC.
          global_context.Get(isolate)->Enter();
          Execute(isolate);
          global_context.Get(isolate)->Exit();
        }
        Shell::FinishExecuting(isolate, global_context);
      }
      Shell::CollectGarbage(isolate);
      done_semaphore_.Signal();
    }

    Shell::ResetOnProfileEndListener(isolate);
  }

  isolate->Dispose();
}

void SourceGroup::StartExecuteInThread() {
  if (thread_ == nullptr) {
    thread_ = new IsolateThread(this);
    CHECK(thread_->Start());
  }
  next_semaphore_.Signal();
}

void SourceGroup::WaitForThread(const i::ParkedScope& parked) {
  if (thread_ == nullptr) return;
  done_semaphore_.ParkedWait(parked);
}

void SourceGroup::JoinThread(const i::ParkedScope& parked) {
  USE(parked);
  if (thread_ == nullptr) return;
  thread_->Join();
}

void SerializationDataQueue::Enqueue(std::unique_ptr<SerializationData> data) {
  base::MutexGuard lock_guard(&mutex_);
  data_.push_back(std::move(data));
}

bool SerializationDataQueue::Dequeue(
    std::unique_ptr<SerializationData>* out_data) {
  out_data->reset();
  base::MutexGuard lock_guard(&mutex_);
  if (data_.empty()) return false;
  *out_data = std::move(data_[0]);
  data_.erase(data_.begin());
  return true;
}

bool SerializationDataQueue::IsEmpty() {
  base::MutexGuard lock_guard(&mutex_);
  return data_.empty();
}

void SerializationDataQueue::Clear() {
  base::MutexGuard lock_guard(&mutex_);
  data_.clear();
}

Worker::Worker(Isolate* parent_isolate, const char* script,
               bool flush_denormals)
    : script_(i::StrDup(script)),
      flush_denormals_(flush_denormals),
      parent_isolate_(parent_isolate) {
  state_.store(State::kReady);
}

Worker::~Worker() {
  CHECK(state_.load() == State::kTerminated);
  DCHECK_NULL(isolate_);
  delete thread_;
  thread_ = nullptr;
  delete[] script_;
  script_ = nullptr;
}

bool Worker::is_running() const { return state_.load() == State::kRunning; }

bool Worker::StartWorkerThread(Isolate* requester,
                               std::shared_ptr<Worker> worker,
                               base::Thread::Priority priority) {
  auto expected = State::kReady;
  CHECK(
      worker->state_.compare_exchange_strong(expected, State::kPrepareRunning));
  auto thread = new WorkerThread(worker, priority);
  worker->thread_ = thread;
  if (!thread->Start()) return false;
  // Wait until the worker is ready to receive messages.
  worker->started_semaphore_.ParkedWait(
      reinterpret_cast<i::Isolate*>(requester)->main_thread_local_isolate());
  Shell::AddRunningWorker(std::move(worker));
  return true;
}

void Worker::WorkerThread::Run() {
  // Prevent a lifetime cycle from Worker -> WorkerThread -> Worker.
  // We must clear the worker_ field of the thread, but we keep the
  // worker alive via a stack root until the thread finishes execution
  // and removes itself from the running set. Thereafter the only
  // remaining reference can be from a JavaScript object via a Managed.
  auto worker = std::move(worker_);
  worker_ = nullptr;
  worker->ExecuteInThread();
  Shell::RemoveRunningWorker(worker);
}

class ProcessMessageTask : public i::CancelableTask {
 public:
  ProcessMessageTask(i::CancelableTaskManager* task_manager,
                     std::shared_ptr<Worker> worker,
                     std::unique_ptr<SerializationData> data)
      : i::CancelableTask(task_manager),
        worker_(worker),
        data_(std::move(data)) {}

  void RunInternal() override { worker_->ProcessMessage(std::move(data_)); }

 private:
  std::shared_ptr<Worker> worker_;
  std::unique_ptr<SerializationData> data_;
};

void Worker::PostMessage(std::unique_ptr<SerializationData> data) {
  base::MutexGuard lock_guard(&worker_mutex_);
  if (!is_running()) return;
  std::unique_ptr<v8::Task> task(new ProcessMessageTask(
      task_manager_, shared_from_this(), std::move(data)));
  task_runner_->PostNonNestableTask(std::move(task));
}

class TerminateTask : public i::CancelableTask {
 public:
  TerminateTask(i::CancelableTaskManager* task_manager,
                std::shared_ptr<Worker> worker)
      : i::CancelableTask(task_manager), worker_(worker) {}

  void RunInternal() override {
    auto expected = Worker::State::kTerminating;
    CHECK(worker_->state_.compare_exchange_strong(expected,
                                                  Worker::State::kTerminated));
  }

 private:
  std::shared_ptr<Worker> worker_;
};

std::unique_ptr<SerializationData> Worker::GetMessage(Isolate* requester) {
  std::unique_ptr<SerializationData> result;
  while (!out_queue_.Dequeue(&result)) {
    // If the worker is no longer running, and there are no messages in the
    // queue, don't expect any more messages from it.
    if (!is_running()) break;
    out_semaphore_.ParkedWait(
        reinterpret_cast<i::Isolate*>(requester)->main_thread_local_isolate());
  }
  return result;
}

std::unique_ptr<SerializationData> Worker::TryGetMessage() {
  std::unique_ptr<SerializationData> result;
  if (!out_queue_.Dequeue(&result)) {
    return nullptr;
  }
  return result;
}

void Worker::TerminateAndWaitForThread(const i::ParkedScope& parked) {
  USE(parked);
  Terminate();
  {
    base::MutexGuard lock_guard(&worker_mutex_);
    // Prevent double-joining.
    if (is_joined_) return;
    is_joined_ = true;
  }
  thread_->Join();
}

void Worker::Terminate() {
  base::MutexGuard lock_guard(&worker_mutex_);
  auto expected = State::kRunning;
  if (!state_.compare_exchange_strong(expected, State::kTerminating)) return;
  std::unique_ptr<v8::Task> task(
      new TerminateTask(task_manager_, shared_from_this()));
  task_runner_->PostTask(std::move(task));
  // Also schedule an interrupt in case the worker is running code and never
  // returning to the event queue. Since we checked the state before, and we are
  // holding the {worker_mutex_}, it's safe to access the isolate.
  isolate_->TerminateExecution();
}

void Worker::EnterTerminatedState() {
  base::MutexGuard lock_guard(&worker_mutex_);
  state_.store(State::kTerminated);
  CHECK(!is_running());
  task_runner_.reset();
  task_manager_ = nullptr;
}

void Worker::ProcessMessage(std::unique_ptr<SerializationData> data) {
  if (!is_running()) return;
  DCHECK_NOT_NULL(isolate_);
  HandleScope scope(isolate_);
  Local<Context> context = context_.Get(isolate_);
  Context::Scope context_scope(context);
  Local<Object> global = context->Global();

  // Get the message handler.
  MaybeLocal<Value> maybe_onmessage = global->Get(
      context, String::NewFromUtf8Literal(isolate_, "onmessage",
                                          NewStringType::kInternalized));
  Local<Value> onmessage;
  if (!maybe_onmessage.ToLocal(&onmessage) || !onmessage->IsFunction()) return;
  Local<Function> onmessage_fun = onmessage.As<Function>();

  v8::TryCatch try_catch(isolate_);
  try_catch.SetVerbose(true);
  Local<Value> value;
  if (Shell::DeserializeValue(isolate_, std::move(data)).ToLocal(&value)) {
    DCHECK(!isolate_->IsExecutionTerminating());
    Local<Object> event = Object::New(isolate_);
    event
        ->CreateDataProperty(
            context,
            String::NewFromUtf8Literal(isolate_, "data",
                                       NewStringType::kInternalized),
            value)
        .ToChecked();
    Local<Value> argv[] = {event};
    MaybeLocal<Value> result = onmessage_fun->Call(context, global, 1, argv);
    USE(result);
  }
  if (isolate_->IsExecutionTerminating()) {
    // Re-schedule an interrupt in case the worker is going to run more code
    // and never return to the event queue.
    isolate_->TerminateExecution();
  }
}

void Worker::ProcessMessages() {
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate_);
  i::SaveAndSwitchContext saved_context(i_isolate, i::Context());
  SealHandleScope shs(isolate_);

  TryCatch try_catch(isolate_);
  try_catch.SetVerbose(true);

  while (is_running() && v8::platform::PumpMessageLoop(
                             g_default_platform, isolate_,
                             platform::MessageLoopBehavior::kWaitForWork)) {
    if (try_catch.HasCaught()) return;
    if (is_running()) {
      MicrotasksScope::PerformCheckpoint(isolate_);
    }
  }
}

// static
void Worker::SetCurrentWorker(Worker* worker) {
  CHECK_NULL(current_worker_);
  current_worker_ = worker;
}

namespace {
// This tag value has been picked arbitrarily between 0 and
// V8_EXTERNAL_POINTER_TAG_COUNT.
constexpr v8::ExternalPointerTypeTag kWorkerTag = 8;
}  // namespace

// static
Worker* Worker::GetCurrentWorker() { return current_worker_; }

void Worker::ExecuteInThread() {
  base::FlushDenormalsScope denormals_scope(flush_denormals_);

  Isolate::CreateParams create_params = GetDefaultIsolateCreateParams();
  isolate_ = Isolate::New(create_params);

  // TODO(mdanylo): can we omit dump disable here?
#ifdef V8_DUMPLING
  v8::internal::Isolate* internal_isolate =
      reinterpret_cast<v8::internal::Isolate*>(isolate_);
  internal_isolate->dumpling_manager()->SetIsolateDumpDisabled();
#endif  // V8_DUMPLING

  // Make the Worker instance available to the whole thread.
  SetCurrentWorker(this);

  task_runner_ = g_default_platform->GetForegroundTaskRunner(isolate_);
  task_manager_ =
      reinterpret_cast<i::Isolate*>(isolate_)->cancelable_task_manager();

  auto expected = State::kPrepareRunning;
  CHECK(state_.compare_exchange_strong(expected, State::kRunning));

  // The Worker is now ready to receive messages.
  started_semaphore_.Signal();

  {
    Isolate::Scope isolate_scope(isolate_);
    D8Console console(isolate_);
    Shell::Initialize(isolate_, &console, false);
    PerIsolateData data(isolate_);

    CHECK(context_.IsEmpty());

    {
      HandleScope scope(isolate_);
      Local<Context> context;
      if (Shell::CreateEvaluationContext(isolate_).ToLocal(&context)) {
        context_.Reset(isolate_, context);
        CHECK(!context_.IsEmpty());
      }
    }

    if (!context_.IsEmpty()) {
      {
        bool success;
        PerIsolateData::RealmScope realm_scope(isolate_, context_);
        {
          HandleScope scope(isolate_);
          Local<Context> context = context_.Get(isolate_);
          Context::Scope context_scope(context);

          Local<Object> global = context->Global();
          Local<Value> this_value = External::New(isolate_, this, kWorkerTag);

          Local<FunctionTemplate> postmessage_fun_template =
              FunctionTemplate::New(isolate_, Worker::PostMessageOut,
                                    this_value);
          Local<Function> postmessage_fun;
          if (postmessage_fun_template->GetFunction(context).ToLocal(
                  &postmessage_fun)) {
            global
                ->Set(
                    context,
                    v8::String::NewFromUtf8Literal(
                        isolate_, "postMessage", NewStringType::kInternalized),
                    postmessage_fun)
                .FromJust();
          }

          Local<FunctionTemplate> close_fun_template =
              FunctionTemplate::New(isolate_, Worker::Close, this_value);
          Local<Function> close_fun;
          if (close_fun_template->GetFunction(context).ToLocal(&close_fun)) {
            global
                ->Set(context,
                      v8::String::NewFromUtf8Literal(
                          isolate_, "close", NewStringType::kInternalized),
                      close_fun)
                .FromJust();
          }

          Local<FunctionTemplate> importScripts_fun_template =
              FunctionTemplate::New(isolate_, Worker::ImportScripts,
                                    this_value);
          Local<Function> importScripts_fun;
          if (importScripts_fun_template->GetFunction(context).ToLocal(
                  &importScripts_fun)) {
            global
                ->Set(context,
                      v8::String::NewFromUtf8Literal(
                          isolate_, "importScripts",
                          NewStringType::kInternalized),
                      importScripts_fun)
                .FromJust();
          }

          // First run the script
          Local<String> file_name =
              String::NewFromUtf8Literal(isolate_, "unnamed");
          Local<String> source =
              String::NewFromUtf8(isolate_, script_).ToLocalChecked();
          success = Shell::ExecuteString(isolate_, source, file_name,
                                         Shell::kReportExceptions);
        }
        if (!Shell::FinishExecuting(isolate_, context_)) success = false;
        if (success) {
          bool handler_present;
          {
            HandleScope scope(isolate_);
            Local<Context> context = context_.Get(isolate_);
            Context::Scope context_scope(context);
            Local<Object> global = context->Global();
            // Check that there's a message handler
            MaybeLocal<Value> maybe_onmessage = global->Get(
                context,
                String::NewFromUtf8Literal(isolate_, "onmessage",
                                           NewStringType::kInternalized));
            Local<Value> onmessage;
            handler_present =
                maybe_onmessage.ToLocal(&onmessage) && onmessage->IsFunction();
          }
          if (handler_present) {
            // Now wait for messages.
            ProcessMessages();
          }
        }
      }
      Shell::CollectGarbage(isolate_);
    }

    EnterTerminatedState();

    Shell::ResetOnProfileEndListener(isolate_);
    context_.Reset();
    platform::NotifyIsolateShutdown(g_default_platform, isolate_);
  }

  isolate_->Dispose();
  isolate_ = nullptr;

  // Post nullptr to wake the thread waiting on GetMessage() if there is one.
  out_queue_.Enqueue(nullptr);
  out_semaphore_.Signal();
  // Also post an cleanup task to the parent isolate, so that it sees that this
  // worker is terminated and can clean it up in a thread-safe way.
  g_platform->GetForegroundTaskRunner(parent_isolate_)
      ->PostTask(std::make_unique<CleanUpWorkerTask>(parent_isolate_,
                                                     this->shared_from_this()));
}

void Worker::PostMessageOut(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate = info.GetIsolate();
  HandleScope handle_scope(isolate);

  if (info.Length() < 1) {
    ThrowError(isolate, "Invalid argument");
    return;
  }

  Local<Value> message = info[0];
  Local<Value> transfer = Undefined(isolate);
  std::unique_ptr<SerializationData> data =
      Shell::SerializeValue(isolate, message, transfer);
  if (data) {
    DCHECK(info.Data()->IsExternal());
    Local<External> this_value = info.Data().As<External>();
    Worker* worker = static_cast<Worker*>(this_value->Value(kWorkerTag));

    worker->out_queue_.Enqueue(std::move(data));
    worker->out_semaphore_.Signal();
    g_platform->GetForegroundTaskRunner(worker->parent_isolate_)
        ->PostTask(std::make_unique<CheckMessageFromWorkerTask>(
            worker->parent_isolate_, worker->shared_from_this()));
  }
}

void Worker::ImportScripts(const v8::FunctionCallbackInfo<v8::Value>& info) {
  Shell::ExecuteFile(info);
}

void Worker::Close(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(i::ValidateCallbackInfo(info));
  Isolate* isolate = info.GetIsolate();
  HandleScope handle_scope(isolate);
  DCHECK(info.Data()->IsExternal());
  Local<External> this_value = info.Data().As<External>();
  Worker* worker = static_cast<Worker*>(this_value->Value(kWorkerTag));
  worker->Terminate();
}

#ifdef V8_TARGET_OS_WIN
// Enable support for unicode filename path on windows.
// We first convert ansi encoded argv[i] to utf16 encoded, and then
// convert utf16 encoded to utf8 encoded with setting the argv[i]
// to the utf8 encoded arg. We allocate memory for the utf8 encoded
// arg, and we will free it and reset it to nullptr after using
// the filename path arg. And because Execute may be called multiple
// times, we need to free the allocated unicode filename when exit.

// Save the allocated utf8 filenames, and we will free them when exit.
std::vector<char*> utf8_filenames;
#include <shellapi.h>
// Convert utf-16 encoded string to utf-8 encoded.
char* ConvertUtf16StringToUtf8(const wchar_t* str) {
  // On Windows wchar_t must be a 16-bit value.
  static_assert(sizeof(wchar_t) == 2, "wrong wchar_t size");
  int len =
      WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, nullptr, FALSE);
  DCHECK_LT(0, len);
  char* utf8_str = new char[len];
  utf8_filenames.push_back(utf8_str);
  WideCharToMultiByte(CP_UTF8, 0, str, -1, utf8_str, len, nullptr, FALSE);
  return utf8_str;
}

// Convert ansi encoded argv[i] to utf8 encoded.
void PreProcessUnicodeFilenameArg(char* argv[], int i) {
  int argc;
  wchar_t** wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
  argv[i] = ConvertUtf16StringToUtf8(wargv[i]);
  LocalFree(wargv);
}

#endif

namespace {

bool FlagMatches(const char* flag, char** arg, bool keep_flag = false) {
  if (strcmp(*arg, flag) == 0) {
    if (!keep_flag) {
      *arg = nullptr;
    }
    return true;
  }
  return false;
}

template <size_t N>
bool FlagWithArgMatches(const char (&flag)[N], char** flag_value, int argc,
                        char* argv[], int* i) {
  char* current_arg = argv[*i];

  // Compare the flag up to the last character of the flag name (not including
  // the null terminator).
  if (strncmp(current_arg, flag, N - 1) == 0) {
    // Match against --flag=value
    if (current_arg[N - 1] == '=') {
      *flag_value = argv[*i] + N;
      argv[*i] = nullptr;
      return true;
    }
    // Match against --flag value
    if (current_arg[N - 1] == '\0') {
      CHECK_LT(*i, argc - 1);
      argv[*i] = nullptr;
      (*i)++;
      *flag_value = argv[*i];
      argv[*i] = nullptr;
      return true;
    }
  }

  flag_value = nullptr;
  return false;
}

}  // namespace

bool Shell::SetOptions(int argc, char* argv[]) {
  options.d8_path = argv[0];
  bool disallow_unsafe_flags = false;
  bool exit_on_flag_contradictions = false;
  for (int i = 0; i < argc; i++) {
    char* flag_value = nullptr;
    if (FlagMatches("--", &argv[i])) {
      i++;
      for (; i < argc; i++) {
        options.arguments.push_back(argv[i]);
        argv[i] = nullptr;
      }
      break;
    } else if (FlagMatches("--no-arguments", &argv[i])) {
      options.include_arguments = false;
    } else if (FlagMatches("--simulate-errors", &argv[i])) {
      options.simulate_errors = true;
    } else if (FlagMatches("--fuzzing", &argv[i], /*keep_flag=*/true) ||
               FlagMatches("--no-abort-on-contradictory-flags", &argv[i],
                           /*keep_flag=*/true) ||
               FlagMatches("--noabort-on-contradictory-flags", &argv[i],
                           /*keep_flag=*/true)) {
      check_d8_flag_contradictions = false;
    } else if (FlagMatches("--abort-on-contradictory-flags", &argv[i],
                           /*keep_flag=*/true)) {
      check_d8_flag_contradictions = true;
    } else if (FlagMatches("--exit-on-contradictory-flags", &argv[i],
                           /*keep_flag=*/true)) {
      check_d8_flag_contradictions = true;
      exit_on_flag_contradictions = true;
    } else if (FlagMatches("--disallow-unsafe-flags", &argv[i],
                           /*keep_flag=*/true)) {
      disallow_unsafe_flags = true;
    } else if (FlagMatches("--shell", &argv[i])) {
      options.interactive_shell = true;
    } else if (FlagMatches("--test", &argv[i])) {
      options.test_shell = true;
    } else if (FlagMatches("--notest", &argv[i]) ||
               FlagMatches("--no-test", &argv[i])) {
      options.test_shell = false;
    } else if (FlagMatches("--send-idle-notification", &argv[i])) {
      options.send_idle_notification = true;
    } else if (FlagMatches("--invoke-weak-callbacks", &argv[i])) {
      options.invoke_weak_callbacks = true;
      // TODO(v8:3351): Invoking weak callbacks does not always collect all
      // available garbage.
      options.send_idle_notification = true;
    } else if (FlagMatches("--omit-quit", &argv[i])) {
      options.omit_quit = true;
    } else if (FlagMatches("--no-wait-for-background-tasks", &argv[i])) {
      // TODO(herhut) Remove this flag once wasm compilation is fully
      // isolate-independent.
      options.wait_for_background_tasks = false;
    } else if (FlagMatches("-f", &argv[i], /*keep_flag=*/true)) {
      // Ignore any -f flags for compatibility with other stand-alone
      // JavaScript engines.
      continue;
    } else if (FlagMatches("--ignore-unhandled-promises", &argv[i])) {
      options.ignore_unhandled_promises = true;
    } else if (FlagMatches("--isolate", &argv[i], /*keep_flag=*/true)) {
      options.num_isolates++;
    } else if (FlagMatches("--throws", &argv[i])) {
      options.expected_to_throw = true;
    } else if (FlagMatches("--no-fail", &argv[i])) {
      options.no_fail = true;
    } else if (FlagMatches("--dump-counters", &argv[i])) {
      options.dump_counters = true;
    } else if (FlagMatches("--dump-counters-nvp", &argv[i])) {
      options.dump_counters_nvp = true;
    } else if (FlagMatches("--dump-system-memory-stats", &argv[i])) {
      options.dump_system_memory_stats = true;
    } else if (FlagWithArgMatches("--icu-data-file", &flag_value, argc, argv,
                                  &i)) {
      options.icu_data_file = flag_value;
    } else if (FlagWithArgMatches("--icu-locale", &flag_value, argc, argv,
                                  &i)) {
      options.icu_locale = flag_value;
#ifdef V8_USE_EXTERNAL_STARTUP_DATA
    } else if (FlagWithArgMatches("--snapshot_blob", &flag_value, argc, argv,
                                  &i)) {
      options.snapshot_blob = flag_value;
#endif  // V8_USE_EXTERNAL_STARTUP_DATA
    } else if (FlagMatches("--cache", &argv[i]) ||
               FlagWithArgMatches("--cache", &flag_value, argc, argv, &i)) {
      if (!flag_value || strcmp(flag_value, "code") == 0) {
        options.compile_options = v8::ScriptCompiler::kNoCompileOptions;
        options.code_cache_options =
            ShellOptions::CodeCacheOptions::kProduceCache;
      } else if (strcmp(flag_value, "none") == 0) {
        options.compile_options = v8::ScriptCompiler::kNoCompileOptions;
        options.code_cache_options = ShellOptions::kNoProduceCache;
      } else if (strcmp(flag_value, "after-execute") == 0) {
        options.compile_options = v8::ScriptCompiler::kNoCompileOptions;
        options.code_cache_options =
            ShellOptions::CodeCacheOptions::kProduceCacheAfterExecute;
      } else if (strcmp(flag_value, "full-code-cache") == 0) {
        options.compile_options = v8::ScriptCompiler::kEagerCompile;
        options.code_cache_options =
            ShellOptions::CodeCacheOptions::kProduceCache;
      } else {
        fprintf(stderr, "Unknown option to --cache.\n");
        return false;
      }
    } else if (FlagMatches("--streaming-compile", &argv[i])) {
      options.streaming_compile = true;
    } else if ((FlagMatches("--no-streaming-compile", &argv[i])) ||
               (FlagMatches("--nostreaming-compile", &argv[i]))) {
      options.streaming_compile = false;
    } else if (FlagMatches("--enable-tracing", &argv[i])) {
      options.trace_enabled = true;
    } else if (FlagWithArgMatches("--trace-path", &flag_value, argc, argv,
                                  &i)) {
      options.trace_path = flag_value;
    } else if (FlagWithArgMatches("--trace-config", &flag_value, argc, argv,
                                  &i)) {
      options.trace_config = flag_value;
    } else if (FlagMatches("--enable-inspector", &argv[i])) {
      options.enable_inspector = true;
    } else if (FlagWithArgMatches("--lcov", &flag_value, argc, argv, &i)) {
      options.lcov_file = flag_value;
#ifdef V8_OS_LINUX
    } else if (FlagMatches("--scope-linux-perf-to-mark-measure", &argv[i])) {
      options.scope_linux_perf_to_mark_measure = true;
    } else if (FlagWithArgMatches("--perf-ctl-fd", &flag_value, argc, argv,
                                  &i)) {
      options.perf_ctl_fd = atoi(flag_value);
    } else if (FlagWithArgMatches("--perf-ack-fd", &flag_value, argc, argv,
                                  &i)) {
      options.perf_ack_fd = atoi(flag_value);
#endif  // V8_OS_LINUX
#ifdef V8_ENABLE_HARDWARE_WATCHPOINT_SUPPORT
    } else if (bool enable_tracing = FlagMatches(
                   "--trace-memory-corruption-via-watchpoints", &argv[i]);
               enable_tracing ||
               FlagMatches("--memory-corruption-via-watchpoints", &argv[i])) {
      // The tracing flag also implies enabling the API.
      options.memory_corruption_via_watchpoints = true;
      options.trace_memory_corruption_via_watchpoints = enable_tracing;
      // Imply --expose-memory-corruption-api.
      i::v8_flags.expose_memory_corruption_api = true;
      // Disable compaction to get stable addresses.
      i::v8_flags.compact = false;
#endif  // V8_ENABLE_HARDWARE_WATCHPOINT_SUPPORT
    } else if (FlagMatches("--disable-in-process-stack-traces", &argv[i])) {
      options.disable_in_process_stack_traces = true;
#ifdef V8_OS_POSIX
    } else if (FlagWithArgMatches("--read-from-tcp-port", &flag_value, argc,
                                  argv, &i)) {
      options.read_from_tcp_port = atoi(flag_value);
#endif  // V8_OS_POSIX
    } else if (FlagMatches("--enable-os-system", &argv[i])) {
      options.enable_os_system = true;
    } else if (FlagMatches("--no-apply-priority", &argv[i])) {
      options.apply_priority = false;
    } else if (FlagMatches("--quiet-load", &argv[i])) {
      options.quiet_load = true;
    } else if (FlagWithArgMatches("--thread-pool-size", &flag_value, argc, argv,
                                  &i)) {
      options.thread_pool_size = atoi(flag_value);
    } else if (FlagMatches("--no-can-block", &argv[i])) {
      options.can_block = false;
    } else if (FlagMatches("--stress-delay-tasks", &argv[i])) {
      // Delay execution of tasks by 0-100ms randomly (based on --random-seed).
      options.stress_delay_tasks = true;
    } else if (FlagMatches("--cpu-profiler", &argv[i])) {
      options.cpu_profiler = true;
    } else if (FlagMatches("--cpu-profiler-print", &argv[i])) {
      options.cpu_profiler = true;
      options.cpu_profiler_print = true;
    } else if (FlagMatches("--stress-deserialize", &argv[i])) {
      options.stress_deserialize = true;
    } else if (FlagMatches("--compile-only", &argv[i])) {
      options.compile_only = true;
    } else if (FlagWithArgMatches("--repeat-compile", &flag_value, argc, argv,
                                  &i)) {
      options.repeat_compile = atoi(flag_value);
    } else if (FlagWithArgMatches("--max-serializer-memory", &flag_value, argc,
                                  argv, &i)) {
      // Value is expressed in MB.
      options.max_serializer_memory = atoi(flag_value) * i::MB;
#ifdef V8_FUZZILLI
    } else if (FlagMatches("--fuzzilli-enable-builtins-coverage", &argv[i])) {
      options.fuzzilli_enable_builtins_coverage = true;
    } else if (FlagMatches("--fuzzilli-coverage-statistics", &argv[i])) {
      options.fuzzilli_coverage_statistics = true;
#endif
    } else if (FlagMatches("--no-fuzzy-module-file-extensions", &argv[i])) {
      DCHECK(options.fuzzy_module_file_extensions);
      options.fuzzy_module_file_extensions = false;
#if defined(V8_ENABLE_ETW_STACK_WALKING)
    } else if (FlagMatches("--enable-etw-stack-walking", &argv[i])) {
      options.enable_etw_stack_walking = true;
      // This needs to be manually triggered for JIT ETW events to work.
      i::v8_flags.enable_etw_stack_walking = true;
#if defined(V8_ENABLE_SYSTEM_INSTRUMENTATION)
    } else if (FlagMatches("--enable-system-instrumentation", &argv[i])) {
      options.enable_system_instrumentation = true;
      options.trace_enabled = true;
#endif
#if defined(V8_OS_WIN)
      // Guard this bc the flag has a lot of overhead and is not currently used
      // by macos
      i::v8_flags.interpreted_frames_native_stack = true;
#endif
#endif
#if V8_ENABLE_WEBASSEMBLY
    } else if (FlagMatches("--wasm-trap-handler", &argv[i])) {
      options.wasm_trap_handler = true;
    } else if (FlagMatches("--no-wasm-trap-handler", &argv[i])) {
      options.wasm_trap_handler = false;
#endif  // V8_ENABLE_WEBASSEMBLY
    } else if (FlagMatches("--expose-fast-api", &argv[i])) {
      options.expose_fast_api = true;
    } else if (FlagMatches("--flush-denormals", &argv[i])) {
      options.flush_denormals = true;
    } else {
#ifdef V8_TARGET_OS_WIN
      PreProcessUnicodeFilenameArg(argv, i);
#endif
    }
  }

  DCHECK(options.num_isolates);

  if (disallow_unsafe_flags) {
    const auto check_flag_is_not_specified =
        [&]<typename T>(const ShellOptions::DisallowReassignment<T>& flag) {
          if (!flag.WasSpecified()) {
            return;
          }
          base::OS::PrintError(
              "Command-line provided flag --%s is prohibited by "
              "--disallow-unsafe-flags.\n",
              flag.name());
          if (exit_on_flag_contradictions) {
            base::OS::ExitProcess(-1);
          } else {
            base::OS::Abort();
          }
        };  // NOLINT(readability/braces)
    // The --disallow-unsafe-flags is meant to block known unsafe configurations
    // and mitigate spurious reports due invalid flag combinations/values. To
    // prevent AI agents and/or fuzzers from using a new unsafe flag, add it to
    // the list below.
    check_flag_is_not_specified(options.trace_enabled);
    check_flag_is_not_specified(options.trace_config);
    check_flag_is_not_specified(options.trace_path);
    check_flag_is_not_specified(options.enable_inspector);
    check_flag_is_not_specified(options.lcov_file);
    check_flag_is_not_specified(options.simulate_errors);
    check_flag_is_not_specified(options.enable_os_system);
    check_flag_is_not_specified(options.snapshot_blob);
    check_flag_is_not_specified(options.thread_pool_size);
    check_flag_is_not_specified(options.thread_pool_size);
    check_flag_is_not_specified(options.dump_counters);
    check_flag_is_not_specified(options.dump_counters_nvp);
  }

#ifdef V8_OS_LINUX
  if (options.scope_linux_perf_to_mark_measure) {
    if (options.perf_ctl_fd == -1 || options.perf_ack_fd == -1) {
      fprintf(stderr,
              "Flag --scope-linux-perf-to-mark-measure requires both "
              "--perf-ctl-fd and --perf-ack-fd\n");
      return false;
    }
    SendPerfControlCommand("disable");
  }
#endif

  const char* usage =
      "Synopsis:\n"
      "  shell [options] [--shell] [<file>...]\n"
      "  d8 [options] [-e <string>] [--shell] [--module|]"
      " <file>...]\n\n"
      "  -e        execute a string in V8\n"
      "  --shell   run an interactive JavaScript shell\n"
      "  --module  execute a file as a JavaScript module\n";
  using HelpOptions = i::FlagList::HelpOptions;
  i::v8_flags.abort_on_contradictory_flags = true;
  static constexpr char kStandaloneD8ShellFlag[] = "--is_standalone_d8_shell";
  i::FlagList::SetFlagsFromString(kStandaloneD8ShellFlag,
                                  strlen(kStandaloneD8ShellFlag));
  i::FlagList::SetFlagsFromCommandLine(&argc, argv, true,
                                       HelpOptions(HelpOptions::kExit, usage));
  i::FlagList::ResolveContradictionsWhenFuzzing();

  options.mock_arraybuffer_allocator = i::v8_flags.mock_arraybuffer_allocator;
  options.mock_arraybuffer_allocator_limit =
      i::v8_flags.mock_arraybuffer_allocator_limit;
#ifdef V8_OS_LINUX
  options.multi_mapped_mock_allocator = i::v8_flags.multi_mapped_mock_allocator;
#endif  // V8_OS_LINUX

  if (i::v8_flags.stress_snapshot && options.expose_fast_api &&
      check_d8_flag_contradictions) {
    FATAL("Flag --expose-fast-api is incompatible with --stress-snapshot.");
  }

  // Set up isolated source groups.
  options.isolate_sources = new SourceGroup[options.num_isolates];
  internal::g_num_isolates_for_testing = options.num_isolates;
  SourceGroup* current = options.isolate_sources;
  current->Begin(argv, 1);
  for (int i = 1; i < argc; i++) {
    const char* str = argv[i];
    if (strcmp(str, "--isolate") == 0) {
      current->End(i);
      current++;
      current->Begin(argv, i + 1);
    } else if (strcmp(str, "--module") == 0 || strcmp(str, "--json") == 0) {
      // Pass on to SourceGroup, which understands these options.
    } else if (strncmp(str, "--", 2) == 0) {
      if (!i::v8_flags.correctness_fuzzer_suppressions) {
        printf("Warning: unknown flag %s.\nTry --help for options\n", str);
      }
    } else if (strcmp(str, "-e") == 0 && i + 1 < argc) {
      set_script_executed();
    } else if (strncmp(str, "-", 1) != 0) {
      // Not a flag, so it must be a script to execute.
      set_script_executed();
    }
  }
  current->End(argc);

  return true;
}

int Shell::RunMain(v8::Isolate* isolate, bool last_run) {
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);

  for (int i = 1; i < options.num_isolates; ++i) {
    options.isolate_sources[i].StartExecuteInThread();
  }

  // The Context object, created inside RunMainIsolate, is used after the method
  // returns in some situations:
  const bool keep_context_alive =
      last_run && (use_interactive_shell() || i::v8_flags.stress_snapshot);
  bool success = RunMainIsolate(isolate, keep_context_alive);
  CollectGarbage(isolate);

  // Park the main thread here to prevent deadlocks in shared GCs when
  // waiting in JoinThread.
  i_isolate->main_thread_local_heap()->ExecuteMainThreadWhileParked(
      [last_run](const i::ParkedScope& parked) {
        for (int i = 1; i < options.num_isolates; ++i) {
          if (last_run) {
            options.isolate_sources[i].JoinThread(parked);
          } else {
            options.isolate_sources[i].WaitForThread(parked);
          }
        }
        WaitForRunningWorkers(parked);
      });

  // Other threads have terminated, we can now run the artificial
  // serialize-deserialize pass (which destructively mutates heap state).
  if (success && last_run && i::v8_flags.stress_snapshot) {
    HandleScope handle_scope(isolate);
    static constexpr bool kClearRecompilableData = true;
    auto context = v8::Local<v8::Context>::New(isolate, evaluation_context_);
    i::DirectHandle<i::Context> i_context = Utils::OpenDirectHandle(*context);
    // Stop concurrent compiles before mutating the heap.
    if (i_isolate->concurrent_recompilation_enabled()) {
      i_isolate->optimizing_compile_dispatcher()->StartTearDown();
      i_isolate->optimizing_compile_dispatcher()->FinishTearDown();
    }
#if V8_ENABLE_MAGLEV
    if (i_isolate->maglev_concurrent_dispatcher()->is_enabled()) {
      i_isolate->maglev_concurrent_dispatcher()->AwaitCompileJobs();
    }
#endif  // V8_ENABLE_MAGLEV
    // TODO(jgruber,v8:10500): Don't deoptimize once we support serialization
    // of optimized code.
    i::Deoptimizer::DeoptimizeAll(i_isolate);
    // Trigger GC to better align with production code. Also needed by
    // ClearReconstructableDataForSerialization to not look into dead objects.
    i_isolate->heap()->CollectAllAvailableGarbage(
        i::GarbageCollectionReason::kSnapshotCreator);
    i::Snapshot::ClearReconstructableDataForSerialization(
        i_isolate, kClearRecompilableData);
    i::Snapshot::SerializeDeserializeAndVerifyForTesting(i_isolate, i_context);
  }

  if (Shell::unhandled_promise_rejections_.load() > 0) {
    printf("%i pending unhandled Promise rejection(s) detected.\n",
           Shell::unhandled_promise_rejections_.load());
    success = false;
    // RunMain may be executed multiple times, e.g. in REPRL mode, so we have to
    // reset this counter.
    Shell::unhandled_promise_rejections_.store(0);
  }
  // In order to finish successfully, success must be != expected_to_throw.
  if (Shell::options.no_fail) return 0;
  // Fuzzers aren't expected to use --throws, but may pick it up from testcases.
  // In that case, just ignore the flag.
  if (i::v8_flags.fuzzing && Shell::options.expected_to_throw) return 0;
  return (success == Shell::options.expected_to_throw ? 1 : 0);
}

bool Shell::RunMainIsolate(v8::Isolate* isolate, bool keep_context_alive) {
  if (options.lcov_file) {
    debug::Coverage::SelectMode(isolate, debug::CoverageMode::kBlockCount);
  }
  HandleScope scope(isolate);
  Global<Context> global_context;
  {
    Local<Context> context;
    if (!CreateEvaluationContext(isolate).ToLocal(&context)) {
      DCHECK(isolate->IsExecutionTerminating());
      // We must not exit early here in REPRL mode as that would cause the next
      // testcase sent by Fuzzilli to be skipped, which will desynchronize the
      // communication between d8 and Fuzzilli, leading to a crash.
      DCHECK(!fuzzilli_reprl);
      return true;
    }
    global_context.Reset(isolate, context);
    if (keep_context_alive) {
      evaluation_context_.Reset(isolate, context);
    }
  }
  PerIsolateData::RealmScope realm_scope(isolate, global_context);
  InspectorClient inspector_client(isolate, global_context,
                                   options.enable_inspector);
  bool success = true;
  {
    // We cannot use a Context::Scope here, as it keeps a local handle to the
    // context and SourceGroup::Execute may execute a non-nestable task, e.g. a
    // stackless GC.
    global_context.Get(isolate)->Enter();
    if (!options.isolate_sources[0].Execute(isolate)) success = false;
    global_context.Get(isolate)->Exit();
  }
  if (!FinishExecuting(isolate, global_context)) success = false;
  WriteLcovData(isolate, options.lcov_file);
  return success;
}

void Shell::CollectGarbage(Isolate* isolate) {
  if (options.send_idle_notification) {
    isolate->ContextDisposedNotification(
        v8::ContextDependants::kSomeDependants);
  }
  if (options.invoke_weak_callbacks) {
    // By sending a low memory notifications, we will try hard to collect all
    // garbage and will therefore also invoke all weak callbacks of actually
    // unreachable persistent handles.
    isolate->LowMemoryNotification();
  }
}

namespace {
bool ProcessMessages(
    Isolate* isolate,
    const std::function<platform::MessageLoopBehavior()>& behavior) {
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  i::SaveAndSwitchContext saved_context(i_isolate, i::Context());
  SealHandleScope shs(isolate);

  if (isolate->IsExecutionTerminating()) return true;
  TryCatch try_catch(isolate);
  try_catch.SetVerbose(true);

  while (true) {
    bool ran_a_task =
        v8::platform::PumpMessageLoop(g_default_platform, isolate, behavior());
    if (isolate->IsExecutionTerminating()) return true;
    if (try_catch.HasCaught()) return false;
    if (ran_a_task) MicrotasksScope::PerformCheckpoint(isolate);
    if (isolate->IsExecutionTerminating()) return true;

    // In predictable mode we push all background tasks into the foreground
    // task queue of the {kProcessGlobalPredictablePlatformWorkerTaskQueue}
    // isolate. We execute all background tasks after running one foreground
    // task.
    if (i::v8_flags.verify_predictable) {
      TryCatch inner_try_catch(isolate);
      inner_try_catch.SetVerbose(true);
      while (v8::platform::PumpMessageLoop(
          g_default_platform, kProcessGlobalPredictablePlatformWorkerTaskQueue,
          platform::MessageLoopBehavior::kDoNotWait)) {
        ran_a_task = true;
        if (inner_try_catch.HasCaught()) return false;
        if (isolate->IsExecutionTerminating()) return true;
      }
    }

    if (!ran_a_task) break;
  }
  if (g_default_platform->IdleTasksEnabled(isolate)) {
    v8::platform::RunIdleTasks(g_default_platform, isolate,
                               50.0 / base::Time::kMillisecondsPerSecond);
    if (try_catch.HasCaught()) return false;
    if (isolate->IsExecutionTerminating()) return true;
  }
  return true;
}
}  // anonymous namespace

bool Shell::CompleteMessageLoop(Isolate* isolate) {
  auto get_waiting_behaviour = [isolate]() {
    if (options.wait_for_background_tasks &&
        isolate->HasPendingBackgroundTasks()) {
      return platform::MessageLoopBehavior::kWaitForWork;
    }
    if (PerIsolateData::Get(isolate)->HasRunningSubscribedWorkers()) {
      return platform::MessageLoopBehavior::kWaitForWork;
    }
    return platform::MessageLoopBehavior::kDoNotWait;
  };
  if (i::v8_flags.verify_predictable) {
    bool ran_tasks = ProcessMessages(
        isolate, [] { return platform::MessageLoopBehavior::kDoNotWait; });
    if (get_waiting_behaviour() ==
        platform::MessageLoopBehavior::kWaitForWork) {
      FATAL(
          "There is outstanding work after executing all tasks in predictable "
          "mode -- this would deadlock.");
    }
    return ran_tasks;
  }
  return ProcessMessages(isolate, get_waiting_behaviour);
}

bool Shell::FinishExecuting(Isolate* isolate, const Global<Context>& context) {
  if (!CompleteMessageLoop(isolate)) return false;
  HandleScope scope(isolate);
  // We cannot use a Context::Scope here, as it keeps a local handle to the
  // context and HandleUnhandledPromiseRejections may execute a non-nestable
  // task, e.g. a stackless GC.
  context.Get(isolate)->Enter();
  bool result = HandleUnhandledPromiseRejections(isolate);
  context.Get(isolate)->Exit();
  return result;
}

bool Shell::EmptyMessageQueues(Isolate* isolate) {
  return ProcessMessages(
      isolate, []() { return platform::MessageLoopBehavior::kDoNotWait; });
}

bool Shell::HandleUnhandledPromiseRejections(Isolate* isolate) {
  if (options.ignore_unhandled_promises) return true;
  PerIsolateData* data = PerIsolateData::Get(isolate);
  int count = data->HandleUnhandledPromiseRejections();
  Shell::unhandled_promise_rejections_.store(
      Shell::unhandled_promise_rejections_.load() + count);
  return count == 0;
}

class Serializer : public ValueSerializer::Delegate {
 public:
  explicit Serializer(Isolate* isolate)
      : isolate_(isolate),
        serializer_(isolate, this),
        current_memory_usage_(0) {}

  Serializer(const Serializer&) = delete;
  Serializer& operator=(const Serializer&) = delete;

  Maybe<bool> WriteValue(Local<Context> context, Local<Value> value,
                         Local<Value> transfer) {
    bool ok;
    DCHECK(!data_);
    data_.reset(new SerializationData);
    if (!PrepareTransfer(context, transfer).To(&ok)) {
      return Nothing<bool>();
    }
    serializer_.WriteHeader();

    if (!serializer_.WriteValue(context, value).To(&ok)) {
      data_.reset();
      return Nothing<bool>();
    }

    if (!FinalizeTransfer().To(&ok)) {
      return Nothing<bool>();
    }

    std::pair<uint8_t*, size_t> pair = serializer_.Release();
    data_->data_.reset(pair.first);
    data_->size_ = pair.second;
    return Just(true);
  }

  std::unique_ptr<SerializationData> Release() { return std::move(data_); }

  void AppendBackingStoresTo(std::vector<std::shared_ptr<BackingStore>>* to) {
    to->insert(to->end(), std::make_move_iterator(backing_stores_.begin()),
               std::make_move_iterator(backing_stores_.end()));
    backing_stores_.clear();
  }

 protected:
  // Implements ValueSerializer::Delegate.
  void ThrowDataCloneError(Local<String> message) override {
    isolate_->ThrowException(Exception::Error(message));
  }

  Maybe<uint32_t> GetSharedArrayBufferId(
      Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer) override {
    DCHECK_NOT_NULL(data_);
    for (size_t index = 0; index < shared_array_buffers_.size(); ++index) {
      if (shared_array_buffers_[index] == shared_array_buffer) {
        return Just<uint32_t>(static_cast<uint32_t>(index));
      }
    }

    size_t index = shared_array_buffers_.size();
    shared_array_buffers_.emplace_back(isolate_, shared_array_buffer);
    data_->sab_backing_stores_.push_back(
        shared_array_buffer->GetBackingStore());
    return Just<uint32_t>(static_cast<uint32_t>(index));
  }

  Maybe<uint32_t> GetWasmModuleTransferId(
      Isolate* isolate, Local<WasmModuleObject> module) override {
    DCHECK_NOT_NULL(data_);
    for (size_t index = 0; index < wasm_modules_.size(); ++index) {
      if (wasm_modules_[index] == module) {
        return Just<uint32_t>(static_cast<uint32_t>(index));
      }
    }

    size_t index = wasm_modules_.size();
    wasm_modules_.emplace_back(isolate_, module);
    data_->compiled_wasm_modules_.push_back(module->GetCompiledModule());
    return Just<uint32_t>(static_cast<uint32_t>(index));
  }

  void* ReallocateBufferMemory(void* old_buffer, size_t size,
                               size_t* actual_size) override {
    // Not accurate, because we don't take into account reallocated buffers,
    // but this is fine for testing.
    current_memory_usage_ += size;
    if (current_memory_usage_ > Shell::options.max_serializer_memory) {
      return nullptr;
    }

    void* result = base::Realloc(old_buffer, size);
    *actual_size = result ? size : 0;
    return result;
  }

  void FreeBufferMemory(void* buffer) override { base::Free(buffer); }

  bool AdoptSharedValueConveyor(Isolate* isolate,
                                SharedValueConveyor&& conveyor) override {
    data_->shared_value_conveyor_.emplace(std::move(conveyor));
    return true;
  }

 private:
  Maybe<bool> PrepareTransfer(Local<Context> context, Local<Value> transfer) {
    if (transfer->IsArray()) {
      Local<Array> transfer_array = transfer.As<Array>();
      uint32_t length = transfer_array->Length();
      for (uint32_t i = 0; i < length; ++i) {
        Local<Value> element;
        if (transfer_array->Get(context, i).ToLocal(&element)) {
          if (!element->IsArrayBuffer()) {
            isolate_->ThrowError(
                "Transfer array elements must be an ArrayBuffer");
            return Nothing<bool>();
          }

          Local<ArrayBuffer> array_buffer = element.As<ArrayBuffer>();

          if (std::find(array_buffers_.begin(), array_buffers_.end(),
                        array_buffer) != array_buffers_.end()) {
            isolate_->ThrowError(
                "ArrayBuffer occurs in the transfer array more than once");
            return Nothing<bool>();
          }

          serializer_.TransferArrayBuffer(
              static_cast<uint32_t>(array_buffers_.size()), array_buffer);
          array_buffers_.emplace_back(isolate_, array_buffer);
        } else {
          return Nothing<bool>();
        }
      }
      return Just(true);
    } else if (transfer->IsUndefined()) {
      return Just(true);
    } else {
      isolate_->ThrowError("Transfer list must be an Array or undefined");
      return Nothing<bool>();
    }
  }

  Maybe<bool> FinalizeTransfer() {
    for (const auto& global_array_buffer : array_buffers_) {
      Local<ArrayBuffer> array_buffer =
          Local<ArrayBuffer>::New(isolate_, global_array_buffer);
      if (!array_buffer->IsDetachable()) {
        isolate_->ThrowError(
            "ArrayBuffer is not detachable and could not be transferred");
        return Nothing<bool>();
      }

      auto backing_store = array_buffer->GetBackingStore();
      data_->backing_stores_.push_back(std::move(backing_store));
      if (array_buffer->Detach(v8::Local<v8::Value>()).IsNothing()) {
        return Nothing<bool>();
      }
    }

    return Just(true);
  }

  // This must come before ValueSerializer as it caches this value.
  Isolate* isolate_;
  ValueSerializer serializer_;
  std::unique_ptr<SerializationData> data_;
  std::vector<Global<ArrayBuffer>> array_buffers_;
  std::vector<Global<SharedArrayBuffer>> shared_array_buffers_;
  std::vector<Global<WasmModuleObject>> wasm_modules_;
  std::vector<std::shared_ptr<v8::BackingStore>> backing_stores_;
  size_t current_memory_usage_;
};

class Deserializer : public ValueDeserializer::Delegate {
 public:
  Deserializer(Isolate* isolate, std::unique_ptr<SerializationData> data)
      : isolate_(isolate),
        deserializer_(isolate, data->data(), data->size(), this),
        data_(std::move(data)) {
    deserializer_.SetSupportsLegacyWireFormat(true);
  }

  Deserializer(const Deserializer&) = delete;
  Deserializer& operator=(const Deserializer&) = delete;

  MaybeLocal<Value> ReadValue(Local<Context> context) {
    bool read_header;
    if (!deserializer_.ReadHeader(context).To(&read_header)) {
      return MaybeLocal<Value>();
    }

    uint32_t index = 0;
    for (const auto& backing_store : data_->backing_stores()) {
      Local<ArrayBuffer> array_buffer =
          ArrayBuffer::New(isolate_, std::move(backing_store));
      deserializer_.TransferArrayBuffer(index++, array_buffer);
    }

    return deserializer_.ReadValue(context);
  }

  MaybeLocal<SharedArrayBuffer> GetSharedArrayBufferFromId(
      Isolate* isolate, uint32_t clone_id) override {
    DCHECK_NOT_NULL(data_);
    if (clone_id < data_->sab_backing_stores().size()) {
      return SharedArrayBuffer::New(
          isolate_, std::move(data_->sab_backing_stores().at(clone_id)));
    }
    return MaybeLocal<SharedArrayBuffer>();
  }

  MaybeLocal<WasmModuleObject> GetWasmModuleFromId(
      Isolate* isolate, uint32_t transfer_id) override {
    DCHECK_NOT_NULL(data_);
    if (transfer_id >= data_->compiled_wasm_modules().size()) return {};
    return WasmModuleObject::FromCompiledModule(
        isolate_, data_->compiled_wasm_modules().at(transfer_id));
  }

  const SharedValueConveyor* GetSharedValueConveyor(Isolate* isolate) override {
    DCHECK_NOT_NULL(data_);
    if (data_->shared_value_conveyor()) {
      return &data_->shared_value_conveyor().value();
    }
    return nullptr;
  }

 private:
  Isolate* isolate_;
  ValueDeserializer deserializer_;
  std::unique_ptr<SerializationData> data_;
};

class D8Testing {
 public:
  /**
   * Get the number of runs of a given test that is required to get the full
   * stress coverage.
   */
  static int GetStressRuns() {
    if (i::v8_flags.stress_runs != 0) return i::v8_flags.stress_runs;
#ifdef DEBUG
    // In debug mode the code runs much slower so stressing will only make two
    // runs.
    return 2;
#else
    return 5;
#endif
  }

  /**
   * Force deoptimization of all functions.
   */
  static void DeoptimizeAll(Isolate* isolate) {
    i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
    i::HandleScope scope(i_isolate);
    i::Deoptimizer::DeoptimizeAll(i_isolate);
  }
};

std::unique_ptr<SerializationData> Shell::SerializeValue(
    Isolate* isolate, Local<Value> value, Local<Value> transfer) {
  bool ok;
  Local<Context> context = isolate->GetCurrentContext();
  Serializer serializer(isolate);
  std::unique_ptr<SerializationData> data;
  if (serializer.WriteValue(context, value, transfer).To(&ok)) {
    data = serializer.Release();
  }
  return data;
}

MaybeLocal<Value> Shell::DeserializeValue(
    Isolate* isolate, std::unique_ptr<SerializationData> data) {
  Local<Context> context = isolate->GetCurrentContext();
  Deserializer deserializer(isolate, std::move(data));
  return deserializer.ReadValue(context);
}

void Shell::AddRunningWorker(std::shared_ptr<Worker> worker) {
  workers_mutex_.Pointer()->AssertHeld();  // caller should hold the mutex.
  running_workers_.insert(worker);
}

void Shell::RemoveRunningWorker(const std::shared_ptr<Worker>& worker) {
  base::MutexGuard lock_guard(workers_mutex_.Pointer());
  auto it = running_workers_.find(worker);
  if (it != running_workers_.end()) running_workers_.erase(it);
}

void Shell::WaitForRunningWorkers(const i::ParkedScope& parked) {
  // Make a copy of running_workers_, because we don't want to call
  // Worker::Terminate while holding the workers_mutex_ lock. Otherwise, if a
  // worker is about to create a new Worker, it would deadlock.
  std::unordered_set<std::shared_ptr<Worker>> workers_copy;
  {
    base::MutexGuard lock_guard(workers_mutex_.Pointer());
    allow_new_workers_ = false;
    workers_copy.swap(running_workers_);
  }

  for (auto& worker : workers_copy) {
    worker->TerminateAndWaitForThread(parked);
  }

  // Now that all workers are terminated, we can re-enable Worker creation.
  base::MutexGuard lock_guard(workers_mutex_.Pointer());
  DCHECK(running_workers_.empty());
  allow_new_workers_ = true;
}

namespace {

#ifdef V8_OS_POSIX
void d8_sigterm_handler(int signal, siginfo_t* info, void* context) {
  // Dump stacktraces when terminating d8 instances with SIGTERM.
  // SIGKILL is not intercepted.
  if (signal == SIGTERM) {
    FATAL("d8: Received SIGTERM signal (likely due to a TIMEOUT)\n");
  } else {
    UNREACHABLE();
  }
}
#endif  // V8_OS_POSIX

void d8_install_sigterm_handler() {
#ifdef V8_OS_POSIX
  CHECK(!i::v8_flags.fuzzing);
  struct sigaction sa;
  sa.sa_sigaction = d8_sigterm_handler;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  if (sigaction(SIGTERM, &sa, NULL) == -1) {
    FATAL("Could not install SIGTERM handler");
  }
#endif  // V8_OS_POSIX
}

void ConfigurePartitionAllocIfEnabled() {
#if defined(V8_ENABLE_PARTITION_ALLOC)
#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
  allocator_shim::ConfigurePartitionsForTesting();
  allocator_shim::internal::PartitionAllocMalloc::Allocator()
      ->EnableThreadCacheIfSupported();
  // Use the same limits Chrome uses for the foreground mode.
  static constexpr size_t kThreadCacheLargeSizeThreshold = 1 << 15;
  ::partition_alloc::ThreadCache::SetLargestCachedSize(
      kThreadCacheLargeSizeThreshold);
  // Adjust slot span ring size to 1024. Keep it aligned with constants in
  // partition_alloc_constants.h
  static constexpr int kForegroundMaxEmptySlotSpansDirtyBytesShift = 2;
  static constexpr int kSlotSpanRingSize = 1 << 10;
  allocator_shim::internal::PartitionAllocMalloc::Allocator()
      ->AdjustSlotSpanRing(kSlotSpanRingSize,
                           kForegroundMaxEmptySlotSpansDirtyBytesShift);
#endif  // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
#endif  // defined(V8_ENABLE_PARTITION_ALLOC)
}

}  // namespace

int Shell::Main(int argc, char* argv[]) {
  ConfigurePartitionAllocIfEnabled();
  v8::base::EnsureConsoleOutput();
  if (!v8::Shell::SetOptions(argc, argv)) return 1;

#ifdef V8_ENABLE_HARDWARE_WATCHPOINT_SUPPORT
  if (v8::Shell::options.memory_corruption_via_watchpoints) {
    bool enable_tracing =
        v8::Shell::options.trace_memory_corruption_via_watchpoints;
    SetupForHardwareWatchpoints(enable_tracing);
  }
#endif  // V8_ENABLE_HARDWARE_WATCHPOINT_SUPPORT

  if (!i::v8_flags.fuzzing) d8_install_sigterm_handler();

  base::FlushDenormalsScope denormals_scope(options.flush_denormals);

  v8::V8::InitializeICUDefaultLocation(argv[0], options.icu_data_file);

#ifdef V8_OS_DARWIN
  if (options.apply_priority) {
    struct task_category_policy category = {.role =
                                                TASK_FOREGROUND_APPLICATION};
    task_policy_set(mach_task_self(), TASK_CATEGORY_POLICY,
                    (task_policy_t)&category, TASK_CATEGORY_POLICY_COUNT);
    pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
  }
#endif

#ifdef V8_INTL_SUPPORT
  if (options.icu_locale != nullptr) {
    icu::Locale locale(options.icu_locale);
    UErrorCode error_code = U_ZERO_ERROR;
    icu::Locale::setDefault(locale, error_code);
  }
#endif  // V8_INTL_SUPPORT

  v8::platform::InProcessStackDumping in_process_stack_dumping =
      options.disable_in_process_stack_traces
          ? v8::platform::InProcessStackDumping::kDisabled
          : v8::platform::InProcessStackDumping::kEnabled;

  std::ofstream trace_file;
  std::unique_ptr<platform::tracing::TracingController> tracing;
  if (options.trace_enabled && !i::v8_flags.verify_predictable) {
    tracing = std::make_unique<platform::tracing::TracingController>();

    if (!options.enable_etw_stack_walking) {
      const char* trace_path = options.trace_path ? options.trace_path :
      // Default to protobuf format when Perfetto is used and without json
      // export.
#if defined(V8_USE_PERFETTO) && !defined(V8_USE_PERFETTO_JSON_EXPORT)
                                                  "v8_trace.pb";
#else
                                                  "v8_trace.json";
#endif  // defined(V8_USE_PERFETTO) && !defined(V8_USE_PERFETTO_JSON_EXPORT)
      trace_file.open(trace_path);
      if (!trace_file.good()) {
        printf("Cannot open trace file '%s' for writing: %s.\n", trace_path,
               strerror(errno));
        return 1;
      }
    }

#ifdef V8_USE_PERFETTO
    // Set up the in-process backend that the tracing controller will connect
    // to.
    perfetto::TracingInitArgs init_args;
    init_args.backends = perfetto::BackendType::kInProcessBackend;
    perfetto::Tracing::Initialize(init_args);

    tracing->InitializeForPerfetto(&trace_file);
#else
    platform::tracing::TraceBuffer* trace_buffer = nullptr;
#if defined(V8_ENABLE_SYSTEM_INSTRUMENTATION)
    if (options.enable_system_instrumentation) {
      trace_buffer =
          platform::tracing::TraceBuffer::CreateTraceBufferRingBuffer(
              platform::tracing::TraceBuffer::kRingBufferChunks,
              platform::tracing::TraceWriter::
                  CreateSystemInstrumentationTraceWriter());
    }
#endif  // V8_ENABLE_SYSTEM_INSTRUMENTATION
    if (!trace_buffer) {
      trace_buffer =
          platform::tracing::TraceBuffer::CreateTraceBufferRingBuffer(
              platform::tracing::TraceBuffer::kRingBufferChunks,
              platform::tracing::TraceWriter::CreateJSONTraceWriter(
                  trace_file));
    }
    tracing->Initialize(trace_buffer);
#endif  // V8_USE_PERFETTO
  }

  v8::SandboxHardwareSupport::InitializeBeforeThreadCreation();

  platform::tracing::TracingController* tracing_controller = tracing.get();
  if (i::v8_flags.single_threaded) {
    g_platform = v8::platform::NewSingleThreadedDefaultPlatform(
        v8::platform::IdleTaskSupport::kEnabled, in_process_stack_dumping,
        std::move(tracing));
  } else {
    g_platform = v8::platform::NewDefaultPlatform(
        options.thread_pool_size, v8::platform::IdleTaskSupport::kEnabled,
        in_process_stack_dumping, std::move(tracing),
        options.apply_priority ? v8::platform::PriorityMode::kApply
                               : v8::platform::PriorityMode::kDontApply);
  }
  g_default_platform = g_platform.get();
  if (i::v8_flags.predictable) {
    g_platform = MakePredictablePlatform(std::move(g_platform));
  }
  if (options.stress_delay_tasks) {
    int64_t random_seed = i::v8_flags.fuzzer_random_seed;
    if (!random_seed) random_seed = i::v8_flags.random_seed;
    // If random_seed is still 0 here, the {DelayedTasksPlatform} will choose a
    // random seed.
    g_platform = MakeDelayedTasksPlatform(std::move(g_platform), random_seed);
  }

  v8::V8::InitializePlatform(g_platform.get());

  // Disable flag freezing if we are producing a code cache, because for that we
  // modify v8_flags.hash_seed (below).
  if (options.code_cache_options != ShellOptions::kNoProduceCache) {
    i::v8_flags.freeze_flags_after_init = false;
  }

  v8::V8::Initialize();
  if (options.snapshot_blob) {
    v8::V8::InitializeExternalStartupDataFromFile(options.snapshot_blob);
  } else {
    v8::V8::InitializeExternalStartupData(argv[0]);
  }
  int result = 0;
  Isolate::CreateParams create_params = GetDefaultIsolateCreateParams();
  ShellArrayBufferAllocator shell_array_buffer_allocator;
  MockArrayBufferAllocator mock_arraybuffer_allocator;
  const size_t memory_limit =
      options.mock_arraybuffer_allocator_limit * options.num_isolates;
  MockArrayBufferAllocatiorWithLimit mock_arraybuffer_allocator_with_limit(
      memory_limit >= options.mock_arraybuffer_allocator_limit
          ? memory_limit
          : std::numeric_limits<size_t>::max());
#ifdef V8_OS_LINUX
  MultiMappedAllocator multi_mapped_mock_allocator;
#endif  // V8_OS_LINUX
  if (options.mock_arraybuffer_allocator) {
    if (memory_limit) {
      Shell::array_buffer_allocator = &mock_arraybuffer_allocator_with_limit;
    } else {
      Shell::array_buffer_allocator = &mock_arraybuffer_allocator;
    }
#ifdef V8_OS_LINUX
  } else if (options.multi_mapped_mock_allocator) {
    Shell::array_buffer_allocator = &multi_mapped_mock_allocator;
#endif  // V8_OS_LINUX
  } else {
    Shell::array_buffer_allocator = &shell_array_buffer_allocator;
  }
  create_params.array_buffer_allocator = Shell::array_buffer_allocator;
#ifdef ENABLE_VTUNE_JIT_INTERFACE
  if (i::v8_flags.enable_vtunejit) {
    create_params.code_event_handler = vTune::GetVtuneCodeEventHandler();
  }
#endif  // ENABLE_VTUNE_JIT_INTERFACE

  Shell::counter_map_ = new CounterMap();
  if (options.dump_counters || options.dump_counters_nvp ||
      i::v8_flags.trace_number_string_cache ||
      i::TracingFlags::is_gc_stats_enabled() ||
      i::v8_flags.map_counters[0] != '\0') {
    create_params.counter_lookup_callback = LookupCounter;
    create_params.create_histogram_callback = CreateHistogram;
    create_params.add_histogram_sample_callback = AddHistogramSample;
  }

#if V8_ENABLE_WEBASSEMBLY
  // TODO(429173713): currently we need to disable the trap handler if hardware
  // sandboxing is active and the kernel version is too old.
  if (!i::HardwareSandboxingDisabledOrSupportsSignalDeliveryInSandbox()) {
    fprintf(stderr, "Disabling Wasm trap handler as the kernel is too old\n");
    options.wasm_trap_handler = false;
  }

  if (V8_TRAP_HANDLER_SUPPORTED && options.wasm_trap_handler) {
    constexpr bool kUseDefaultTrapHandler = true;
    if (!v8::V8::EnableWebAssemblyTrapHandler(kUseDefaultTrapHandler)) {
      FATAL("Could not register trap handler");
    }
  }
#endif  // V8_ENABLE_WEBASSEMBLY

  if (i::v8_flags.experimental) {
    // This message is printed to stderr so that it is also visible in
    // Clusterfuzz reports.
    fprintf(stderr,
            "V8 is running with experimental features enabled. Stability and "
            "security will suffer.\n");
  }

  if (i::v8_flags.test_only_unsafe) {
    fprintf(stderr,
            "V8 is running with an unsupported configuration. Important "
            "subsystems are mocked or disabled. Bugs reported under this "
            "configuration will be considered invalid.\n");
  }

  Isolate* isolate = Isolate::New(create_params);

#ifdef V8_FUZZILLI

  if (options.fuzzilli_enable_builtins_coverage) {
    cov_init_builtins_edges(static_cast<uint32_t>(
        i::BasicBlockProfiler::Get()
            ->GetCoverageBitmap(reinterpret_cast<i::Isolate*>(isolate))
            .size()));
  }

  // Let the parent process (Fuzzilli) know we are ready.
  char helo[] = "HELO";
  if (write(REPRL_CWFD, helo, 4) != 4 || read(REPRL_CRFD, helo, 4) != 4) {
    fuzzilli_reprl = false;
  }

  if (memcmp(helo, "HELO", 4) != 0) {
    FATAL("REPRL: Invalid response from parent");
  }
#endif  // V8_FUZZILLI

  {
    Isolate::Scope scope(isolate);
    D8Console console(isolate);
    Initialize(isolate, &console);
    PerIsolateData data(isolate);

    // Fuzzilli REPRL = read-eval-print-loop
    do {
#ifdef V8_FUZZILLI
      if (fuzzilli_reprl) {
        unsigned action = 0;
        ssize_t nread = read(REPRL_CRFD, &action, 4);
        if (nread != 4 || action != 'cexe') {
          FATAL("REPRL: Unknown action: %u", action);
        }
      }
#endif  // V8_FUZZILLI
#ifdef V8_DUMPLING
      v8::internal::Isolate* internal_isolate =
          reinterpret_cast<v8::internal::Isolate*>(isolate);
      // TODO(mdanylo): ideally this should be a part of fd-based protocol
      // between Fuzzilli and V8.
      if (internal_isolate->dumpling_manager()->IsDumpingEnabled()) {
        internal_isolate->dumpling_manager()->PrepareForNextREPRLCycle();
      }
#endif  // V8_DUMPLING

      result = 0;

      if (options.trace_enabled) {
        platform::tracing::TraceConfig* trace_config;
        if (options.trace_config) {
          int size = 0;
          char* trace_config_json_str = ReadChars(options.trace_config, &size);
          trace_config = tracing::CreateTraceConfigFromJSON(
              isolate, trace_config_json_str);
          delete[] trace_config_json_str;
        } else {
          trace_config =
              platform::tracing::TraceConfig::CreateDefaultTraceConfig();
          if (options.enable_system_instrumentation) {
            trace_config->AddIncludedCategory("disabled-by-default-v8.compile");
          }
        }
        tracing_controller->StartTracing(trace_config);
      }

      CpuProfiler* cpu_profiler;
      if (options.cpu_profiler) {
        cpu_profiler = CpuProfiler::New(isolate);
        cpu_profiler->StartProfiling(String::Empty(isolate),
                                     CpuProfilingOptions{});
      }

      if (i::v8_flags.stress_runs > 0) {
        options.stress_runs = i::v8_flags.stress_runs;
        for (int i = 0; i < options.stress_runs && result == 0; i++) {
          printf("============ Run %d/%d ============\n", i + 1,
                 options.stress_runs.get());
          bool last_run = i == options.stress_runs - 1;
          result = RunMain(isolate, last_run);
        }
      } else if (options.code_cache_options != ShellOptions::kNoProduceCache) {
        // Park the main thread here in case the new isolate wants to perform
        // a shared GC to prevent a deadlock.
        reinterpret_cast<i::Isolate*>(isolate)
            ->main_thread_local_isolate()
            ->ExecuteMainThreadWhileParked([&result]() {
              printf("============ Run: Produce code cache ============\n");
              // First run to produce the cache
              Isolate::CreateParams create_params2 =
                  GetDefaultIsolateCreateParams();
              // Use a different hash seed.
              i::v8_flags.hash_seed = i::v8_flags.hash_seed ^ 1337;
              Isolate* isolate2 = Isolate::New(create_params2);
              // Restore old hash seed.
              i::v8_flags.hash_seed = i::v8_flags.hash_seed ^ 1337;
              {
                Isolate::Scope isolate_scope(isolate2);
                D8Console console2(isolate2);
                Initialize(isolate2, &console2);
                PerIsolateData data2(isolate2);

                result = RunMain(isolate2, false);
                ResetOnProfileEndListener(isolate2);
              }
              // D8WasmAsyncResolvePromiseTask may be still in the runner at
              // this point. We need to terminate the task runners before the
              // Isolate to avoid retaining stray tasks with v8::Global pointing
              // into a reclaimed Isolate.
              platform::NotifyIsolateShutdown(g_default_platform, isolate2);
              isolate2->Dispose();
            });

        // Change the options to consume cache
        DCHECK(options.compile_options == v8::ScriptCompiler::kEagerCompile ||
               options.compile_options ==
                   v8::ScriptCompiler::kNoCompileOptions);
        options.compile_options.Overwrite(
            v8::ScriptCompiler::kConsumeCodeCache);
        options.code_cache_options.Overwrite(ShellOptions::kNoProduceCache);

        printf("============ Run: Consume code cache ============\n");
        // Second run to consume the cache in current isolate
        result = RunMain(isolate, true);
        options.compile_options.Overwrite(
            v8::ScriptCompiler::kNoCompileOptions);
      } else {
        bool last_run = true;
        result = RunMain(isolate, last_run);
      }

      // Run interactive shell if explicitly requested or if no script has been
      // executed, but never on --test
      if (use_interactive_shell()) {
        RunShell(isolate);
      }

      if (i::v8_flags.trace_ignition_dispatches_output_file != nullptr) {
        WriteIgnitionDispatchCountersFile(isolate);
      }

      if (options.cpu_profiler) {
        CpuProfile* profile =
            cpu_profiler->StopProfiling(String::Empty(isolate));
        if (options.cpu_profiler_print) {
          const internal::ProfileNode* root =
              reinterpret_cast<const internal::ProfileNode*>(
                  profile->GetTopDownRoot());
          root->Print(0);
        }
        profile->Delete();
        cpu_profiler->Dispose();
      }

#ifdef V8_DUMPLING
      // Need to make sure that dump file is fully readable by Fuzzilli.
      // TODO(mdanylo): ideally this should be a part of fd-based protocol
      // between Fuzzilli and V8.
      if (internal_isolate->dumpling_manager()->IsDumpingEnabled()) {
        internal_isolate->dumpling_manager()->FinishCurrentREPRLCycle();
      }
#endif  // V8_DUMPLING

#ifdef V8_FUZZILLI
      // Send result to parent (fuzzilli) and reset edge guards.
      if (fuzzilli_reprl) {
        int status = result << 8;
        std::vector<bool> bitmap;
        if (options.fuzzilli_enable_builtins_coverage) {
          bitmap = i::BasicBlockProfiler::Get()->GetCoverageBitmap(
              reinterpret_cast<i::Isolate*>(isolate));
          cov_update_builtins_basic_block_coverage(bitmap);
        }
        if (options.fuzzilli_coverage_statistics) {
          int tot = 0;
          for (bool b : bitmap) {
            if (b) tot++;
          }
          static int iteration_counter = 0;
          std::ofstream covlog("covlog.txt", std::ios::app);
          covlog << iteration_counter << "\t" << tot << "\t"
                 << sanitizer_cov_count_discovered_edges() << "\t"
                 << bitmap.size() << std::endl;
          iteration_counter++;
        }
        // In REPRL mode, stdout and stderr can be regular files, so they need
        // to be flushed after every execution
        fflush(stdout);
        fflush(stderr);
        CHECK_EQ(write(REPRL_CWFD, &status, 4), 4);
        sanitizer_cov_reset_edgeguards();
        if (options.fuzzilli_enable_builtins_coverage) {
          i::BasicBlockProfiler::Get()->ResetCounts(
              reinterpret_cast<i::Isolate*>(isolate));
        }
      }
#endif  // V8_FUZZILLI
    } while (fuzzilli_reprl);

    // Shut down contexts and collect garbage.
    cached_code_map_.clear();
    evaluation_context_.Reset();
    stringify_function_.Reset();
    ResetOnProfileEndListener(isolate);
    CollectGarbage(isolate);
  }
  OnExit(isolate, true);

  // Delete the platform explicitly here to write the tracing output to the
  // tracing file.
  if (options.trace_enabled) {
    tracing_controller->StopTracing();
  }
  g_platform.reset();

#ifdef V8_TARGET_OS_WIN
  // We need to free the allocated utf8 filenames in
  // PreProcessUnicodeFilenameArg.
  for (char* utf8_str : utf8_filenames) {
    delete[] utf8_str;
  }
  utf8_filenames.clear();
#endif

  return result;
}

}  // namespace v8

int main(int argc, char* argv[]) { return v8::Shell::Main(argc, argv); }

#undef CHECK
#undef DCHECK
