// 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/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes.h"

#include "src/wasm/wasm-module-builder.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(CStrVector("main"), f);
}
#define EMIT_CODE_WITH_END(f, code)  \
  do {                               \
    f->EmitCode(code, sizeof(code)); \
    f->Emit(kExprEnd);               \
  } while (false)

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);
    Handle<WasmMemoryObject> memory_object =
        WasmMemoryObject::New(isolate, 16, 100, SharedFlag::kNotShared)
            .ToHandleChecked();
    Handle<JSArrayBuffer> buffer(memory_object->array_buffer(), isolate);
    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);
    Handle<WasmMemoryObject> memory_object =
        WasmMemoryObject::New(isolate, 16, 100, SharedFlag::kNotShared)
            .ToHandleChecked();
    ManuallyExternalizedBuffer external(
        handle(memory_object->array_buffer(), isolate));
    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);
    WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
    ExportAsMain(f);
    byte code[] = {WASM_GROW_MEMORY(WASM_I32V_1(6)), WASM_DROP,
                   WASM_MEMORY_SIZE};
    EMIT_CODE_WITH_END(f, code);

    ZoneBuffer buffer(&zone);
    builder->WriteTo(&buffer);
    testing::SetupIsolateForWasmModule(isolate);
    ErrorThrower thrower(isolate, "Test");
    const Handle<WasmInstanceObject> instance =
        CompileAndInstantiateForTesting(
            isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()))
            .ToHandleChecked();
    Handle<WasmMemoryObject> memory_object(instance->memory_object(), isolate);

    // Fake the Embedder flow by externalizing the array buffer.
    ManuallyExternalizedBuffer external1(
        handle(memory_object->array_buffer(), isolate));

    // 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(
        handle(memory_object->array_buffer(), isolate));

    // Grow using an internal Wasm bytecode.
    result = testing::CallWasmFunctionForTesting(isolate, instance, "main", 0,
                                                 nullptr);
    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

#undef EMIT_CODE_WITH_END