// Copyright 2019 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 "src/objects/js-array-buffer-inl.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes.h"
#include "test/cctest/cctest.h"
#include "test/cctest/manually-externalized-buffer.h"
#include "test/common/wasm/flag-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"

namespace v8 {
namespace internal {
namespace wasm {
namespace test_grow_memory {

using testing::CompileAndInstantiateForTesting;
using v8::internal::testing::ManuallyExternalizedBuffer;

namespace {
void ExportAsMain(WasmFunctionBuilder* f) {
  f->builder()->AddExport(base::CStrVector("main"), f);
}

void Cleanup(Isolate* isolate = CcTest::InitIsolateOnce()) {
  // 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.
  reinterpret_cast<v8::Isolate*>(isolate)->LowMemoryNotification();
}
}  // namespace

TEST(GrowMemDetaches) {
  {
    Isolate* isolate = CcTest::InitIsolateOnce();
    HandleScope scope(isolate);
    DirectHandle<WasmMemoryObject> memory_object =
        WasmMemoryObject::New(isolate, 16, 100, SharedFlag{false},
                              wasm::AddressType::kI32)
            .ToHandleChecked();
    DirectHandle<JSArrayBuffer> buffer =
        WasmMemoryObject::GetArrayBuffer(isolate, memory_object);
    int32_t result = WasmMemoryObject::Grow(isolate, memory_object, 0);
    CHECK_EQ(16, result);
    CHECK_NE(*buffer, memory_object->array_buffer());
    CHECK(buffer->was_detached());
  }
  Cleanup();
}

TEST(Externalized_GrowMemMemSize) {
  {
    Isolate* isolate = CcTest::InitIsolateOnce();
    HandleScope scope(isolate);
    DirectHandle<WasmMemoryObject> memory_object =
        WasmMemoryObject::New(isolate, 16, 100, SharedFlag{false},
                              wasm::AddressType::kI32)
            .ToHandleChecked();
    ManuallyExternalizedBuffer external(
        WasmMemoryObject::GetArrayBuffer(isolate, memory_object));
    int32_t result = WasmMemoryObject::Grow(isolate, memory_object, 0);
    CHECK_EQ(16, result);
    CHECK_NE(*external.buffer_, memory_object->array_buffer());
    CHECK(external.buffer_->was_detached());
  }
  Cleanup();
}

TEST(Run_WasmModule_Buffer_Externalized_GrowMem) {
  {
    Isolate* isolate = CcTest::InitIsolateOnce();
    HandleScope scope(isolate);
    TestSignatures sigs;
    v8::internal::AccountingAllocator allocator;
    Zone zone(&allocator, ZONE_NAME);

    WasmModuleBuilder* builder = zone.New<WasmModuleBuilder>(&zone);
    builder->AddMemory(16);
    WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
    ExportAsMain(f);
    f->EmitCode({WASM_MEMORY_GROW(WASM_I32V_1(6)), WASM_DROP, WASM_MEMORY_SIZE,
                 WASM_END});

    ZoneBuffer buffer(&zone);
    builder->WriteTo(&buffer);
    ErrorThrower thrower(isolate, "Test");
    const DirectHandle<WasmInstanceObject> instance =
        CompileAndInstantiateForTesting(isolate, &thrower,
                                        base::VectorOf(buffer))
            .ToHandleChecked();
    DirectHandle<WasmMemoryObject> memory_object{
        instance->trusted_data(isolate)->memory_object(0), isolate};

    // Fake the Embedder flow by externalizing the array buffer.
    ManuallyExternalizedBuffer external1(
        WasmMemoryObject::GetArrayBuffer(isolate, memory_object));

    // Grow using the API.
    uint32_t result = WasmMemoryObject::Grow(isolate, memory_object, 4);
    CHECK_EQ(16, result);
    CHECK(external1.buffer_->was_detached());  // growing always detaches
    CHECK_EQ(0, external1.buffer_->byte_length());

    CHECK_NE(*external1.buffer_, memory_object->array_buffer());

    // Fake the Embedder flow by externalizing the array buffer.
    ManuallyExternalizedBuffer external2(
        WasmMemoryObject::GetArrayBuffer(isolate, memory_object));

    // Grow using an internal Wasm bytecode.
    result = testing::CallWasmFunctionForTesting(isolate, instance, "main", {});
    CHECK_EQ(26, result);
    CHECK(external2.buffer_->was_detached());  // growing always detaches
    CHECK_EQ(0, external2.buffer_->byte_length());
    CHECK_NE(*external2.buffer_, memory_object->array_buffer());
  }
  Cleanup();
}

}  // namespace test_grow_memory
}  // namespace wasm
}  // namespace internal
}  // namespace v8
