// 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 "include/v8-context.h"
#include "include/v8-exception.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "src/base/vector.h"
#include "src/execution/isolate.h"
#include "src/objects/property-descriptor.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/fuzzing/random-module-generation.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-feature-flags.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-subtyping.h"
#include "src/zone/accounting-allocator.h"
#include "src/zone/zone.h"
#include "test/common/flag-utils.h"
#include "test/common/wasm/fuzzer-common.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"
#include "test/fuzzer/wasm/interpreter/interpreter-fuzzer-common.h"

namespace fuzzing = v8::internal::wasm::fuzzing;

// This fuzzer fuzzes initializer expressions used e.g. in globals.
// The fuzzer creates a set of globals with initializer expressions and a set of
// functions containing the same body as these initializer expressions.
// The global value should be equal to the result of running the corresponding
// function.

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

#define CHECK_FLOAT_EQ(expected, actual)                    \
  if (std::isnan(expected)) {                               \
    CHECK(std::isnan(actual));                              \
  } else {                                                  \
    CHECK_EQ(expected, actual);                             \
    CHECK_EQ(std::signbit(expected), std::signbit(actual)); \
  }

namespace {
bool IsNullOrWasmNull(Tagged<Object> obj) {
  return IsNull(obj) || IsWasmNull(obj);
}

DirectHandle<Object> GetExport(Isolate* isolate,
                               DirectHandle<WasmInstanceObject> instance,
                               const char* name) {
  DirectHandle<JSObject> exports_object;
  DirectHandle<Name> exports =
      isolate->factory()->InternalizeUtf8String("exports");
  exports_object = Cast<JSObject>(
      JSObject::GetProperty(isolate, instance, exports).ToHandleChecked());

  DirectHandle<Name> main_name =
      isolate->factory()->NewStringFromAsciiChecked(name);
  PropertyDescriptor desc;
  Maybe<bool> property_found = JSReceiver::GetOwnPropertyDescriptor(
      isolate, exports_object, main_name, &desc);
  CHECK(property_found.FromMaybe(false));
  return desc.value();
}

void CheckEquivalent(const WasmValue& init_lhs, const WasmValue& init_rhs,
                     const WasmModule& module) {
  DisallowGarbageCollection no_gc;
  // Stack of elements to be checked.
  std::vector<std::pair<WasmValue, WasmValue>> cmp = {{init_lhs, init_rhs}};
  using TaggedT = decltype(Tagged<Object>().ptr());
  // Map of lhs objects we have already seen to their rhs object on the first
  // visit. This is needed to ensure a reasonable runtime for the check.
  // Example:
  //   (array.new $myArray 10 (array.new_default $myArray 10))
  // This creates a nested array where each outer array element is the same
  // inner array. Without memorizing the inner array, we'd end up performing
  // 100+ comparisons.
  std::unordered_map<TaggedT, TaggedT> lhs_map;
  auto SeenAlready = [&lhs_map](Tagged<Object> lhs, Tagged<Object> rhs) {
    auto [iter, inserted] = lhs_map.insert({lhs.ptr(), rhs.ptr()});
    if (inserted) return false;
    CHECK_EQ(iter->second, rhs.ptr());
    return true;
  };

  auto CheckArray = [&cmp, &SeenAlready](Tagged<Object> lhs,
                                         Tagged<Object> rhs) {
    if (SeenAlready(lhs, rhs)) return;
    CHECK(IsWasmArray(lhs));
    CHECK(IsWasmArray(rhs));
    Tagged<WasmArray> lhs_array = Cast<WasmArray>(lhs);
    Tagged<WasmArray> rhs_array = Cast<WasmArray>(rhs);
    CHECK_EQ(lhs_array->map(), rhs_array->map());
    CHECK_EQ(lhs_array->length(), rhs_array->length());
    cmp.reserve(cmp.size() + lhs_array->length());
    for (uint32_t i = 0; i < lhs_array->length(); ++i) {
      cmp.emplace_back(lhs_array->GetElement(i), rhs_array->GetElement(i));
    }
  };

  auto CheckStruct = [&cmp, &SeenAlready](Tagged<Object> lhs,
                                          Tagged<Object> rhs) {
    if (SeenAlready(lhs, rhs)) return;
    CHECK(IsWasmStruct(lhs));
    CHECK(IsWasmStruct(rhs));
    Tagged<WasmStruct> lhs_struct = Cast<WasmStruct>(lhs);
    Tagged<WasmStruct> rhs_struct = Cast<WasmStruct>(rhs);
    CHECK_EQ(lhs_struct->map(), rhs_struct->map());
    const CanonicalStructType* type = GetTypeCanonicalizer()->LookupStruct(
        lhs_struct->map()->wasm_type_info()->type_index());
    uint32_t field_count = type->field_count();
    for (uint32_t i = 0; i < field_count; ++i) {
      cmp.emplace_back(lhs_struct->GetFieldValue(i),
                       rhs_struct->GetFieldValue(i));
    }
  };

  // Compare the function result with the global value.
  while (!cmp.empty()) {
    const auto [lhs, rhs] = cmp.back();
    cmp.pop_back();
    CHECK_EQ(lhs.type(), rhs.type());
    if (lhs.type().is_numeric()) {
      switch (lhs.type().numeric_kind()) {
        case NumericKind::kI32:
          CHECK_EQ(lhs.to_i32(), rhs.to_i32());
          break;
        case NumericKind::kI64:
          CHECK_EQ(lhs.to_i64(), rhs.to_i64());
          break;
        case NumericKind::kF32:
          CHECK_FLOAT_EQ(lhs.to_f32(), rhs.to_f32());
          break;
        case NumericKind::kF64:
          CHECK_FLOAT_EQ(lhs.to_f64(), rhs.to_f64());
          break;
        case NumericKind::kS128:
          CHECK_EQ(lhs.to_s128(), rhs.to_s128());
          break;
        case NumericKind::kI8:
          CHECK_EQ(lhs.to_i8(), rhs.to_i8());
          break;
        case NumericKind::kI16:
          CHECK_EQ(lhs.to_i16(), rhs.to_i16());
          break;
        default:
          UNIMPLEMENTED();
      }
    } else if (lhs.type().has_index()) {
      Tagged<Object> lhs_ref = *lhs.to_ref();
      Tagged<Object> rhs_ref = *rhs.to_ref();
      CHECK_EQ(IsNull(lhs_ref), IsNull(rhs_ref));
      CHECK_EQ(IsWasmNull(lhs_ref), IsWasmNull(rhs_ref));
      if (IsNullOrWasmNull(lhs_ref)) continue;
      if (lhs.type().ref_type_kind() == RefTypeKind::kStruct) {
        CheckStruct(lhs_ref, rhs_ref);
      } else if (lhs.type().ref_type_kind() == RefTypeKind::kArray) {
        CheckArray(lhs_ref, rhs_ref);
      } else {
        DCHECK(lhs.type().ref_type_kind() == RefTypeKind::kFunction);
        CHECK_EQ(lhs_ref, rhs_ref);
      }
    } else {
      switch (lhs.type().generic_kind()) {
        case GenericKind::kFunc:
        case GenericKind::kI31: {
          Tagged<Object> lhs_ref = *lhs.to_ref();
          Tagged<Object> rhs_ref = *rhs.to_ref();
          CHECK_EQ(lhs_ref, rhs_ref);
          break;
        }
        case GenericKind::kNoFunc:
        case GenericKind::kNone:
        case GenericKind::kNoExn: {
          Tagged<Object> lhs_ref = *lhs.to_ref();
          Tagged<Object> rhs_ref = *rhs.to_ref();
          CHECK(IsWasmNull(lhs_ref));
          CHECK(IsWasmNull(rhs_ref));
          break;
        }
        case GenericKind::kNoExtern: {
          Tagged<Object> lhs_ref = *lhs.to_ref();
          Tagged<Object> rhs_ref = *rhs.to_ref();
          CHECK(IsNull(lhs_ref));
          CHECK(IsNull(rhs_ref));
          break;
        }
        case GenericKind::kExtern:
        case GenericKind::kAny:
        case GenericKind::kEq:
        case GenericKind::kArray:
        case GenericKind::kStruct: {
          Tagged<Object> lhs_ref = *lhs.to_ref();
          Tagged<Object> rhs_ref = *rhs.to_ref();
          if (IsNullOrWasmNull(lhs_ref)) break;
          if (IsWasmStruct(lhs_ref)) {
            CheckStruct(lhs_ref, rhs_ref);
          } else if (IsWasmArray(lhs_ref)) {
            CheckArray(lhs_ref, rhs_ref);
          } else if (IsSmi(lhs_ref)) {
            CHECK_EQ(lhs_ref, rhs_ref);
          }
          break;
        }
        default:
          UNIMPLEMENTED();
      }
    }
  }
}

// Forked from test\fuzzer\wasm\init-expr.cc to work with the Wasm interpreter.
// TODO(choongwoo.han@microsoft.com): Deduplicate code.

void FuzzIt(base::Vector<const uint8_t> data) {
  i::wasm::fuzzing::InitializeDrumbrakeForFuzzing();

  v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
  v8::Isolate* isolate = support->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
  v8::Isolate::Scope isolate_scope(isolate);

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

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

  // We explicitly enable staged WebAssembly features here to increase fuzzer
  // coverage. For libfuzzer fuzzers it is not possible that the fuzzer enables
  // the flag by itself.
  EnableExperimentalWasmFeatures(isolate);

  v8::TryCatch try_catch(isolate);
  HandleScope scope(i_isolate);

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

  size_t expression_count = 0;
  base::Vector<const uint8_t> bytes =
      GenerateWasmModuleForInitExpressions(&zone, data, &expression_count);

  ModuleWireBytes wire_bytes(bytes.begin(), bytes.end());
  auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
  bool valid = GetWasmEngine()->SyncValidate(i_isolate, enabled_features,
                                             CompileTimeImportsForFuzzing(),
                                             wire_bytes.module_bytes());

  if (v8_flags.wasm_fuzzer_gen_test) {
    GenerateTestCase(i_isolate, wire_bytes, valid);
  }

  CHECK(valid);
  FlagScope<bool> eager_compile(&v8_flags.wasm_lazy_compilation, false);
  ErrorThrower thrower(i_isolate, "WasmFuzzerSyncCompile");
  MaybeDirectHandle<WasmModuleObject> compiled_module =
      GetWasmEngine()->SyncCompile(i_isolate, enabled_features,
                                   CompileTimeImportsForFuzzing(), &thrower,
                                   base::OwnedCopyOf(bytes));
  CHECK(!compiled_module.is_null());
  CHECK(!thrower.error());
  thrower.Reset();
  CHECK(!i_isolate->has_exception());

  DirectHandle<WasmModuleObject> module_object =
      compiled_module.ToHandleChecked();
  DirectHandle<WasmInstanceObject> instance =
      GetWasmEngine()
          ->SyncInstantiate(i_isolate, &thrower, module_object, {})
          .ToHandleChecked();
  CHECK_EQ(expression_count,
           module_object->native_module()->module()->num_declared_functions);

  for (size_t i = 0; i < expression_count; ++i) {
    char buffer[22];
    snprintf(buffer, sizeof buffer, "f%zu", i);
    // Execute corresponding function.
    auto function =
        Cast<WasmExportedFunction>(GetExport(i_isolate, instance, buffer));
    DirectHandle<Object> undefined = i_isolate->factory()->undefined_value();
    DirectHandle<Object> function_result =
        Execution::Call(i_isolate, function, undefined, {}).ToHandleChecked();
    // Get global value.
    snprintf(buffer, sizeof buffer, "g%zu", i);
    auto global =
        Cast<WasmGlobalObject>(GetExport(i_isolate, instance, buffer));
    switch (global->unsafe_type().kind()) {
      case ValueKind::kF32: {
        float global_val = global->GetF32();
        float func_val;
        if (IsSmi(*function_result)) {
          func_val = Smi::ToInt(*function_result);
        } else {
          CHECK(IsHeapNumber(*function_result));
          func_val = Cast<HeapNumber>(*function_result)->value();
        }
        CHECK_FLOAT_EQ(func_val, global_val);
        break;
      }
      case ValueKind::kF64: {
        double global_val = global->GetF64();
        double func_val;
        if (IsSmi(*function_result)) {
          func_val = Smi::ToInt(*function_result);
        } else {
          CHECK(IsHeapNumber(*function_result));
          func_val = Cast<HeapNumber>(*function_result)->value();
        }
        CHECK_FLOAT_EQ(func_val, global_val);
        break;
      }
      case ValueKind::kI32: {
        int32_t global_val = global->GetI32();
        int32_t func_val;
        if (IsSmi(*function_result)) {
          func_val = Smi::ToInt(*function_result);
        } else {
          CHECK(IsHeapNumber(*function_result));
          func_val = Cast<HeapNumber>(*function_result)->value();
        }
        CHECK_EQ(func_val, global_val);
        break;
      }
      case ValueKind::kI64: {
        int64_t global_val = global->GetI64();
        int64_t func_val;
        if (IsSmi(*function_result)) {
          func_val = Smi::ToInt(*function_result);
        } else {
          CHECK(IsBigInt(*function_result));
          bool lossless;
          func_val = Cast<BigInt>(*function_result)->AsInt64(&lossless);
          CHECK(lossless);
        }
        CHECK_EQ(func_val, global_val);
        break;
      }
      case ValueKind::kRef:
      case ValueKind::kRefNull: {
        // For reference types the expectations are more limited.
        // Any struct.new would create a new object, so reference equality
        // comparisons will not work.
        DirectHandle<Object> global_val = global->GetRef();
        CHECK_EQ(IsUndefined(*global_val), IsUndefined(*function_result));
        CHECK_EQ(IsNullOrWasmNull(*global_val),
                 IsNullOrWasmNull(*function_result));
        if (!IsNullOrWasmNull(*global_val)) {
          if (IsSubtypeOf(global->unsafe_type(), kWasmFuncRef,
                          module_object->native_module()->module())) {
            // For any function the global should be an internal function
            // whose external function equals the call result. (The call goes
            // through JS conversions while the global is accessed directly.)
            CHECK(IsWasmFuncRef(*global_val));
            CHECK(
                WasmExportedFunction::IsWasmExportedFunction(*function_result));
            CHECK(*WasmInternalFunction::GetOrCreateExternal(direct_handle(
                      Cast<WasmFuncRef>(*global_val)->internal(i_isolate),
                      i_isolate)) == *function_result);
          } else {
            // On arrays and structs, perform a deep comparison.
            DisallowGarbageCollection no_gc;
            WasmValue global_value =
                instance->trusted_data(i_isolate)->GetGlobalValue(
                    i_isolate,
                    instance->trusted_data(i_isolate)->module()->globals[i]);
            WasmValue func_value(function_result, global_value.type());
            CheckEquivalent(global_value, func_value,
                            *module_object->native_module()->module());
          }
        }
        break;
      }
      default:
        UNIMPLEMENTED();
    }
  }
}

}  // anonymous namespace

V8_SYMBOL_USED extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
  v8_fuzzer::FuzzerSupport::InitializeFuzzerSupport(argc, argv);
  return 0;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
  FuzzIt({data, size});
  return 0;
}

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