// Copyright 2024 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 "test/fuzzer/wasm/interpreter/interpreter-fuzzer-common.h"

#include <ctime>

#include "include/v8-context.h"
#include "include/v8-exception.h"
#include "include/v8-function.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-metrics.h"
#include "src/api/api-inl.h"
#include "src/execution/isolate.h"
#include "src/objects/managed-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/property-descriptor.h"
#include "src/utils/ostreams.h"
#include "src/wasm/baseline/liftoff-compiler.h"
#include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/interpreter/wasm-interpreter-runtime.h"
#include "src/wasm/interpreter/wasm-interpreter.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-feature-flags.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes-inl.h"
#include "src/zone/accounting-allocator.h"
#include "src/zone/zone.h"
#include "test/common/wasm/flag-utils.h"
#include "test/common/wasm/fuzzer-common.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"

namespace v8::internal::wasm::fuzzing {

std::mt19937_64 MersenneTwister(const char* data, size_t size) {
  std::mt19937_64 rng;
  std::string str = std::string(data, size);
  std::size_t hash = std::hash<std::string>()(str);
  rng.seed(hash);
  return rng;
}

class WasmInterpretationResult {
 public:
  static WasmInterpretationResult Failed() { return {kFailed, 0, false}; }
  static WasmInterpretationResult Trapped(bool possible_nondeterminism) {
    return {kTrapped, 0, possible_nondeterminism};
  }
  static WasmInterpretationResult Finished(int32_t result,
                                           bool possible_nondeterminism) {
    return {kFinished, result, possible_nondeterminism};
  }

  // {failed()} captures different reasons: The module was invalid, no function
  // to call was found in the module, the function did not terminate within a
  // limited number of steps, or a stack overflow happened.
  bool failed() const { return status_ == kFailed; }
  bool trapped() const { return status_ == kTrapped; }
  bool finished() const { return status_ == kFinished; }

  int32_t result() const {
    DCHECK_EQ(status_, kFinished);
    return result_;
  }

  bool possible_nondeterminism() const { return possible_nondeterminism_; }

 private:
  enum Status { kFinished, kTrapped, kFailed };

  const Status status_;
  const int32_t result_;
  const bool possible_nondeterminism_;

  WasmInterpretationResult(Status status, int32_t result,
                           bool possible_nondeterminism)
      : status_(status),
        result_(result),
        possible_nondeterminism_(possible_nondeterminism) {}
};

// Interprets the given module, starting at the function specified by
// {function_index}. The return type of the function has to be int32. The module
// should not have any imports or exports.
WasmInterpretationResult FastInterpretWasmModule(
    Isolate* isolate, DirectHandle<WasmInstanceObject> instance,
    int32_t function_index, const std::vector<WasmValue>& args,
    std::vector<WasmValue>& rets) {
  Zone zone(isolate->allocator(), ZONE_NAME);
  v8::internal::HandleScope scope(isolate);
  const WasmFunction* func =
      &instance->trusted_data(isolate)->module()->functions[function_index];

  CHECK(func->exported);
  // This would normally be handled by export wrappers.
  if (!IsJSCompatibleSignature(
          reinterpret_cast<const wasm::CanonicalSig*>(func->sig))) {
    return WasmInterpretationResult::Trapped(false);
  }

  CHECK(v8_flags.wasm_jitless);
  isolate->clear_exception();

  wasm::WasmInterpreterThread* thread =
      wasm::WasmInterpreterThread::GetCurrentInterpreterThread(isolate);

  DirectHandle<WasmTrustedInstanceData> trusted_data =
      direct_handle(instance->trusted_data(isolate), isolate);
  // SANDBOX SAFETY: the in-cage instance->trusted_data() handle is
  // attacker-swappable, so re-validate that it still belongs to {instance}
  // before using its interpreter handle. This preserves the check that the
  // removed WasmTrustedInstanceData::GetOrCreateInterpreterObject performed.
  SBXCHECK(trusted_data->has_instance_object() &&
           trusted_data->instance_object() == *instance);

  // Assume an instance can run in only one thread.
  DirectHandle<TrustedManaged<wasm::InterpreterHandle>> handle =
      wasm::GetOrCreateInterpreterHandle(isolate, trusted_data);

  for (const WasmValue& arg : args) {
    if (arg.type().is_ref()) {
      // We should pass WasmNull as null argument, not a JS null value.
      CHECK(!IsNull(*arg.to_ref()));
    }
  }
  bool success = handle->raw()->Execute(
      thread, 0, static_cast<uint32_t>(function_index), args, rets);

  // Returned values should not be the hole value.
  if (success) {
    for (auto& wasm_value : rets) {
      if (wasm_value.type().is_ref()) CHECK(!IsTheHole(*wasm_value.to_ref()));
    }
  }

  return success ? WasmInterpretationResult::Finished(0, false)
                 : WasmInterpretationResult::Failed();
}

Handle<JSObject> GetOrCreateModuleNamespaceObject(Isolate* isolate,
                                                  Handle<JSObject> ffi_object,
                                                  Handle<String> module_name) {
  PropertyDescriptor desc;
  Maybe<bool> property_found = JSReceiver::GetOwnPropertyDescriptor(
      isolate, ffi_object, module_name, &desc);
  if (property_found.FromMaybe(false)) {
    return Cast<JSObject>(desc.value());
  }

  Handle<JSObject> module_namespace =
      isolate->factory()->NewJSObject(isolate->object_function());
  JSObject::DefinePropertyOrElementIgnoreAttributes(ffi_object, module_name,
                                                    module_namespace)
      .Assert();
  return module_namespace;
}

Handle<JSFunction> GenerateJSFunction(Isolate* isolate) {
  v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
  v8::Local<v8::String> fn_body =
      v8::String::NewFromUtf8(v8_isolate, "return arguments[1] + 0x414141;")
          .ToLocalChecked();
  v8::ScriptCompiler::Source script_source(fn_body);
  v8::Local<v8::Function> function =
      v8::ScriptCompiler::CompileFunction(v8_isolate->GetCurrentContext(),
                                          &script_source)
          .ToLocalChecked();

  return Cast<JSFunction>(
      v8::Utils::OpenHandle(*v8::Local<v8::Function>::Cast(function)));
}

MaybeDirectHandle<WasmTableObject> GenerateWasmTable(
    Isolate* isolate, DirectHandle<WasmModuleObject> module_object,
    uint32_t table_index) {
  CppGCManaged<NativeModule>::Ptr native_module =
      module_object->native_module();
  const WasmTable& table = native_module->module()->tables[table_index];

  uint32_t table_initial = 10;
  uint32_t table_maximum = 30;
  bool has_maximum = true;

  if (table.type == wasm::kWasmAnyRef) {
    DirectHandle<WasmTableObject> table_obj = WasmTableObject::New(
        isolate, Handle<WasmTrustedInstanceData>(), wasm::kWasmAnyRef,
        wasm::CanonicalValueType{wasm::kWasmAnyRef}, table_initial, has_maximum,
        table_maximum, isolate->factory()->undefined_value(),
        wasm::AddressType::kI32);
    // Anyref (externref) Table
    Handle<FixedArray> externref_backing_store{table_obj->entries(), isolate};
    externref_backing_store->set(0, Smi::FromInt(0x414141));
    externref_backing_store->set(
        1, *isolate->factory()->NewJSObject(isolate->object_function()));
    return table_obj;
  } else if (table.type == wasm::kWasmFuncRef) {
    return WasmTableObject::New(
        isolate, Handle<WasmTrustedInstanceData>(), wasm::kWasmFuncRef,
        wasm::CanonicalValueType{wasm::kWasmFuncRef}, table_initial,
        has_maximum, table_maximum, isolate->factory()->null_value(),
        wasm::AddressType::kI32);
  }
  // Not supported type
  return {};
}

Handle<String> ExtractUtf8StringFromModuleBytes(
    Isolate* isolate, base::Vector<const uint8_t> wire_bytes,
    wasm::WireBytesRef ref) {
  base::Vector<const uint8_t> name_vec =
      wire_bytes.SubVector(ref.offset(), ref.end_offset());
  return isolate->factory()->InternalizeUtf8String(
      base::Vector<const char>::cast(name_vec));
}

Handle<JSObject> CreateImportObjectInternal(
    Isolate* isolate, DirectHandle<WasmModuleObject> module_object) {
  Handle<JSObject> ffi_object =
      isolate->factory()->NewJSObject(isolate->object_function());

  for (size_t index = 0;
       index < module_object->native_module()->module()->import_table.size();
       ++index) {
    CppGCManaged<NativeModule>::Ptr native_module =
        module_object->native_module();
    const WasmImport& import = native_module->module()->import_table[index];

    Handle<String> module_name = ExtractUtf8StringFromModuleBytes(
        isolate, module_object->native_module()->wire_bytes(),
        import.module_name);

    Handle<String> field_name = ExtractUtf8StringFromModuleBytes(
        isolate, module_object->native_module()->wire_bytes(),
        import.field_name);

    Handle<JSObject> module_namespace =
        GetOrCreateModuleNamespaceObject(isolate, ffi_object, module_name);

    switch (import.kind) {
      case kExternalFunction: {
        // TODO(paolosev@microsoft.com): Support other types of external
        // functions such as wasm? It currently supports only external JS
        // function.
        Handle<JSFunction> fn_obj = GenerateJSFunction(isolate);
        JSObject::DefinePropertyOrElementIgnoreAttributes(module_namespace,
                                                          field_name, fn_obj)
            .Assert();
        break;
      }
      case kExternalTable: {
        uint32_t table_index = import.index;
        DirectHandle<WasmTableObject> table_obj;
        if (GenerateWasmTable(isolate, module_object, table_index)
                .ToHandle(&table_obj)) {
          JSObject::DefinePropertyOrElementIgnoreAttributes(
              module_namespace, field_name, table_obj)
              .Assert();
        }
        break;
      }
      case kExternalMemory: {
        // Memory
        SharedFlag shared = SharedFlag{false};
        int memory_initial = 1;
        int memory_maximum = 32;
        DirectHandle<WasmMemoryObject> memory_obj;
        if (WasmMemoryObject::New(isolate, memory_initial, memory_maximum,
                                  shared, wasm::AddressType::kI32)
                .ToHandle(&memory_obj)) {
          JSObject::DefinePropertyOrElementIgnoreAttributes(
              module_namespace, field_name, memory_obj)
              .Assert();
        }
        break;
      }
      case kExternalGlobal: {
        // Global
        const uint32_t offset = 0;
        const WasmGlobal& global =
            native_module->module()->globals[import.index];
        DirectHandle<WasmTrustedInstanceData> trusted_data =
            WasmTrustedInstanceData::New(isolate, module_object,
                                         native_module.as_shared_ptr());
        MaybeDirectHandle<WasmGlobalObject> maybe_global_obj =
            WasmGlobalObject::New(isolate, trusted_data,
                                  MaybeHandle<WasmGlobalObject::BufferType>(),
                                  global.type, offset, global.mutability);
        DirectHandle<WasmGlobalObject> global_obj;
        if (maybe_global_obj.ToHandle(&global_obj)) {
          JSObject::DefinePropertyOrElementIgnoreAttributes(
              module_namespace, field_name, global_obj)
              .Assert();
        }
        break;
      }
      case kExternalTag: {
        // Not supported yet.
        break;
      }
      default:
        UNREACHABLE();
    }
  }

  return ffi_object;
}

DirectHandle<JSObject> CreateImportObject(
    i::Isolate* isolate, DirectHandle<WasmModuleObject> module_object) {
  return CreateImportObjectInternal(isolate, module_object);
}

bool InstantiateModule(Isolate* isolate,
                       DirectHandle<WasmModuleObject> module_object,
                       DirectHandle<internal::JSObject> imports_obj,
                       DirectHandle<WasmInstanceObject>* instance) {
  ErrorThrower thrower(isolate, "WebAssembly Instantiation");

  if (!GetWasmEngine()
           ->SyncInstantiate(isolate, &thrower, module_object, imports_obj)
           .ToHandle(instance)) {
    isolate->clear_exception();
    thrower.Reset();  // Ignore errors.
    return false;
  }
  return true;
}

// Generate an array of default arguments for the given signature, to be used in
// the interpreter.
std::vector<WasmValue> FastMakeDefaultInterpreterArguments(
    Isolate* isolate, const WasmModule* module, const FunctionSig* sig,
    std::mt19937_64 mt_generator_64) {
  size_t param_count = sig->parameter_count();
  std::vector<WasmValue> arguments(param_count);
  int64_t rand_num = 0;

  for (size_t i = 0; i < param_count; ++i) {
    rand_num = mt_generator_64();
    switch (sig->GetParam(i).kind()) {
      case kI32:
        arguments[i] = WasmValue(static_cast<int32_t>(rand_num));
        break;
      case kI64:
        arguments[i] = WasmValue(rand_num);
        break;
      case kF32:
        arguments[i] = WasmValue(static_cast<float>(rand_num));
        break;
      case kF64:
        arguments[i] = WasmValue(static_cast<double>(rand_num));
        break;
      case kRefNull: {
        ValueType type = sig->GetParam(i);
        wasm::CanonicalValueType canonical_type = wasm::kWasmBottom;
        if (type.has_index()) {
          canonical_type =
              type.Canonicalize(module->canonical_type_id(type.ref_index()));
        } else {
          canonical_type = CanonicalValueType{type};
        }

        if (type.AsNullable() == wasm::kWasmExternRef) {
          arguments[i] = WasmValue(
              Cast<Object>(isolate->factory()->NewHeapNumber(rand_num + 0.125)),
              canonical_type);
        } else {
          arguments[i] =
              WasmValue(isolate->factory()->wasm_null(), canonical_type);
        }
        break;
      }
      case kS128: {
        int64_t rand_num2 = mt_generator_64();
        Simd128 simd_value(Simd128::int64x2{rand_num, rand_num2});
        arguments[i] = WasmValue(simd_value);
        break;
      }
      case kRef:
      case kI8:
      case kI16:
      case kF16:
      case kVoid:
      case kTop:
      case kBottom:
        UNREACHABLE();
    }
  }

  return arguments;
}

Address CreateSyntheticInterpreterEntryFrame(
    void* frame_space, Address caller_fp,
    DirectHandle<WasmInstanceObject> instance, uint32_t function_index) {
  Address frame_ptr = reinterpret_cast<Address>(frame_space);

  // Set up the frame with the minimal required content
  // 1. Store the caller's frame pointer
  *(reinterpret_cast<Address*>(frame_ptr)) = caller_fp;
  // 2. Frame marker
  *(reinterpret_cast<Address*>(frame_ptr + kSystemPointerSize)) =
      StackFrame::TypeToMarker(StackFrame::WASM_INTERPRETER_ENTRY);
  // 3. Store instance pointer
  *(reinterpret_cast<DirectHandle<WasmInstanceObject>*>(
      frame_ptr + 2 * kSystemPointerSize)) = instance;
  // 4. Store function index
  *(reinterpret_cast<uint32_t*>(frame_ptr + 3 * kSystemPointerSize)) =
      function_index;

  return frame_ptr;
}

void RunInstance(Isolate* isolate, std::mt19937_64 rand_generator,
                 DirectHandle<WasmModuleObject> module,
                 DirectHandle<WasmInstanceObject> instance) {
  wasm::WasmInterpreterThread* thread =
      wasm::WasmInterpreterThread::GetCurrentInterpreterThread(isolate);
  const WasmModule* wasm_module = instance->trusted_data(isolate)->module();
  size_t num_exports = wasm_module->export_table.size();
  Address prev_fp = reinterpret_cast<Address>(__builtin_frame_address(0));
  for (size_t i = 0; i < num_exports; ++i) {
    WasmExport exp = wasm_module->export_table[i];

    if (exp.kind != kExternalFunction) continue;
    WasmFunction wfn = wasm_module->functions[exp.index];

    std::vector<WasmValue> arguments = FastMakeDefaultInterpreterArguments(
        isolate, wasm_module, wfn.sig, rand_generator);
    std::vector<WasmValue> retvals(wfn.sig->return_count());

    // Allocate space on stack for the synthetic frame
    constexpr int kFrameSize = 4 * kSystemPointerSize;
    void* frame_space = alloca(kFrameSize);
    thread->set_fuzzer_entry_frame_pointer(CreateSyntheticInterpreterEntryFrame(
        frame_space, prev_fp, instance, wfn.func_index));
    FastInterpretWasmModule(isolate, instance, wfn.func_index, arguments,
                            retvals);
    thread->reset_fuzzer_entry_frame_pointer();

    isolate->clear_exception();
  }  // end handle loop
}

int FastInterpretAndExecuteModule(Isolate* isolate,
                                  DirectHandle<WasmModuleObject> module_object,
                                  std::mt19937_64 rand_generator) {
  // We do not instantiate the module if there is a start function, because a
  // start function can contain an infinite loop which we cannot handle.
  if (module_object->native_module()->module()->start_function_index >= 0)
    return -1;

  HandleScope handle_scope(isolate);  // Avoid leaking handles.

  DirectHandle<JSObject> imports_obj =
      CreateImportObject(isolate, module_object);

  DirectHandle<WasmInstanceObject> other_instance;
  if (!InstantiateModule(isolate, module_object, imports_obj, &other_instance))
    return -1;

  DirectHandle<WasmInstanceObject> instance;
  if (!InstantiateModule(isolate, module_object, imports_obj, &instance))
    return -1;

  RunInstance(isolate, rand_generator, module_object, other_instance);
  RunInstance(isolate, rand_generator, module_object, instance);
  return 0;
}

int FastInterpretAndExecuteModules(
    i::Isolate* isolate, DirectHandle<WasmModuleObject> module_object,
    DirectHandle<WasmModuleObject> other_module_object,
    std::mt19937_64 rand_generator) {
  CppGCManaged<wasm::NativeModule>::Ptr native_module =
      module_object->native_module();
  CppGCManaged<wasm::NativeModule>::Ptr other_native_module =
      other_module_object->native_module();
  if (native_module->module()->start_function_index >= 0) return -1;
  const WasmModule* other_module = other_native_module->module();
  if (other_module->start_function_index >= 0) return -1;

  HandleScope handle_scope(isolate);  // Avoid leaking handles.

  DirectHandle<JSObject> other_imports_obj =
      CreateImportObject(isolate, other_module_object);

  DirectHandle<WasmInstanceObject> other_instance;
  if (!InstantiateModule(isolate, other_module_object, other_imports_obj,
                         &other_instance)) {
    return -1;
  }

  Handle<JSObject> imports_obj =
      CreateImportObjectInternal(isolate, module_object);

  for (size_t i = 0; i < other_module->export_table.size(); ++i) {
    WasmExport exp = other_module->export_table[i];

    if (exp.kind != kExternalFunction) continue;

    Handle<String> field_name = ExtractUtf8StringFromModuleBytes(
        isolate, other_module_object->native_module()->wire_bytes(), exp.name);
    std::unique_ptr<char[]> name = field_name->ToCString();

    MaybeDirectHandle<WasmExportedFunction> maybe_export =
        testing::GetExportedFunction(isolate, other_instance, name.get());

    Handle<String> module_name =
        isolate->factory()->NewStringFromAsciiChecked("fuzzer_module");
    Handle<JSObject> module_namespace =
        GetOrCreateModuleNamespaceObject(isolate, imports_obj, module_name);
    JSObject::DefinePropertyOrElementIgnoreAttributes(
        module_namespace, field_name, maybe_export.ToHandleChecked())
        .Assert();
  }

  DirectHandle<WasmInstanceObject> instance;
  if (!InstantiateModule(isolate, module_object, imports_obj, &instance)) {
    return -1;
  }

  RunInstance(isolate, rand_generator, module_object, instance);
  return 0;
}

void InitializeDrumbrakeForFuzzing() {
  // We should always pass jitless for wasm-jitless.
  v8_flags.jitless = true;
  v8_flags.wasm_jitless = true;

  // This configuration will make fuzzing slower, but helps to reproduce GC bugs
  // more reliably.
  v8_flags.stress_compaction = true;
  v8_flags.gc_interval = 500;

  // Avoid restarting the fuzzer process due to timeout when there is an
  // infinite loop.
  v8_flags.drumbrake_fuzzer_timeout = true;

  // Enable the isolate to be in a state where it can be used for fuzzing.
  i::v8_flags.drumbrake_fuzzing_mode = true;

  // We reduce the maximum memory size and table size of WebAssembly instances
  // to avoid OOMs in the fuzzer.
  v8_flags.wasm_max_mem_pages = 32;
  v8_flags.wasm_max_table_size = 100;

  FlagList::EnforceFlagImplications();

  WasmInterpreterThread::Initialize();
}

int LLVMFuzzerTestOneInputCommon(const uint8_t* data, size_t size,
                                 GenerateModuleFunc generate_module) {
  auto rnd_gen = MersenneTwister(reinterpret_cast<const char*>(data), size);

  wasm::fuzzing::InitializeDrumbrakeForFuzzing();

  v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
  v8::Isolate* isolate = support->GetIsolate();

  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  // Clear any pending exceptions from a prior run.
  if (i_isolate->has_exception()) {
    i_isolate->clear_exception();
  }

  v8::Isolate::Scope isolate_scope(isolate);
  v8::HandleScope handle_scope(isolate);
  v8::Context::Scope context_scope(support->GetContext());

  // Drumbrake may not support some experimental features yet.
  // wasm::fuzzing::EnableExperimentalWasmFeatures(isolate);

  v8::TryCatch try_catch(isolate);

  AccountingAllocator allocator;
  Zone zone(&allocator, ZONE_NAME);

  // Clear recursive groups: The fuzzer creates random types in every run. These
  // are saved as recursive groups as part of the type canonicalizer, but types
  // from previous runs just waste memory.
  ResetTypeCanonicalizer(isolate);

  wasm::ZoneBuffer buffer(&zone);

  if (!generate_module(i_isolate, &zone, {data, size}, &buffer)) {
    return 0;
  }

  wasm::ModuleWireBytes wire_bytes(buffer.begin(), buffer.end());

  HandleScope scope(i_isolate);
  wasm::ErrorThrower thrower(i_isolate, "wasm fuzzer");
  DirectHandle<WasmModuleObject> module_object;
  auto enabled_features = wasm::WasmEnabledFeatures::FromIsolate(i_isolate);
  bool compiles = wasm::GetWasmEngine()
                      ->SyncCompile(i_isolate, enabled_features,
                                    fuzzing::CompileTimeImportsForFuzzing(),
                                    &thrower, v8::base::OwnedCopyOf(buffer))
                      .ToHandle(&module_object);
  // TODO(338326645): add similar GenerateTestCase code for wasm fast
  // interpreter.
  // if (v8_flags.wasm_fuzzer_gen_test) {
  //   wasm::fuzzing::GenerateTestCase(i_isolate, wire_bytes, compiles);
  // }
  int module_result = -1;
  if (compiles) {
    module_result =
        FastInterpretAndExecuteModule(i_isolate, module_object, rnd_gen);
  }
  thrower.Reset();

  // Pump the message loop and run micro tasks, e.g. GC finalization tasks.
  support->PumpMessageLoop(v8::platform::MessageLoopBehavior::kDoNotWait);
  isolate->PerformMicrotaskCheckpoint();
  return module_result;
}

int LLVMFuzzerTestTwoModulesCommon(
    const uint8_t* data, size_t size,
    GenerateExportDataMultipleModulesFunc generate_module) {
  auto rnd_gen = MersenneTwister(reinterpret_cast<const char*>(data), size);

  wasm::fuzzing::InitializeDrumbrakeForFuzzing();

  v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
  v8::Isolate* isolate = support->GetIsolate();

  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  // Clear any pending exceptions from a prior run.
  if (i_isolate->has_exception()) {
    i_isolate->clear_exception();
  }

  v8::Isolate::Scope isolate_scope(isolate);
  v8::HandleScope handle_scope(isolate);
  v8::Context::Scope context_scope(support->GetContext());

  // Drumbrake may not support some experimental features yet.
  // wasm::fuzzing::EnableExperimentalWasmFeatures(isolate);

  v8::TryCatch try_catch(isolate);

  AccountingAllocator allocator;
  Zone zone(&allocator, ZONE_NAME);

  // Clear recursive groups: The fuzzer creates random types in every run. These
  // are saved as recursive groups as part of the type canonicalizer, but types
  // from previous runs just waste memory.
  ResetTypeCanonicalizer(isolate);

  wasm::ZoneBuffer buffer(&zone);

  std::vector<ExportData> exports;
  if (!generate_module(i_isolate, &zone, {data, size}, &buffer, &exports,
                       nullptr)) {
    return 0;
  }

  wasm::ZoneBuffer buffer_2(&zone);
  if (!generate_module(i_isolate, &zone, {data, size}, &buffer_2, nullptr,
                       &exports)) {
    return 0;
  }

  HandleScope scope(i_isolate);
  wasm::ErrorThrower thrower(i_isolate, "wasm fuzzer");
  DirectHandle<WasmModuleObject> module_object;
  auto enabled_features = wasm::WasmEnabledFeatures::FromIsolate(i_isolate);
  bool compiles = wasm::GetWasmEngine()
                      ->SyncCompile(i_isolate, enabled_features,
                                    fuzzing::CompileTimeImportsForFuzzing(),
                                    &thrower, v8::base::OwnedCopyOf(buffer_2))
                      .ToHandle(&module_object);
  DirectHandle<WasmModuleObject> other_module_object;
  bool compiles_2 = wasm::GetWasmEngine()
                        ->SyncCompile(i_isolate, enabled_features,
                                      fuzzing::CompileTimeImportsForFuzzing(),
                                      &thrower, v8::base::OwnedCopyOf(buffer))
                        .ToHandle(&other_module_object);
  int execute_module_result = -1;
  if (compiles && compiles_2) {
    execute_module_result = FastInterpretAndExecuteModules(
        i_isolate, module_object, other_module_object, rnd_gen);
  }
  thrower.Reset();
  return execute_module_result;
}

}  // namespace v8::internal::wasm::fuzzing
