/*
 * Copyright (c) 2021, 2022 SAP SE. All rights reserved.
 * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include "memory/allocation.hpp"
#include "nmt/memTracker.hpp"
#include "nmt/nmtPreInit.hpp"
#include "runtime/os.hpp"
#include "utilities/debug.hpp"
#include "utilities/ostream.hpp"
#include "unittest.hpp"

// convenience log. switch on if debugging tests. Don't use tty, plain stdio only.
//#define LOG(...) { printf(__VA_ARGS__); printf("\n"); fflush(stdout); }
#define LOG(...)


// This tests the ability of the NMT pre-init system to deal with various combinations
// of pre- and post-init-allocations.

// The tests consist of two phases:
// 1) before NMT initialization (pre-NMT-init) we allocate and reallocate a bunch of
//    blocks via os::malloc() and os::realloc(), and free some of them via os::free()
// 2) after NMT initialization, we reallocate some more, then free all of them.
//
// The intent is to check that blocks allocated in pre-init phase and potentially realloced
// in pre-init phase are handled correctly if further realloc'ed or free'd post-init.

// We manage to run tests in different phases with this technique:
// - for the pre-init phase, we start the tests in the constructor of a global object; that constructor will
//   run as part of the dyn. C++ initialization of the gtestlauncher binary. Since the gtestlauncher links
//   *statically* against the libjvm, gtestlauncher and libjvm initialization fold into one and are the same.
// - for the post-init phase, we just start it inside a TEST_VM scope, which needs to create the VM for
//   us. So inside that scope VM initialization ran and with it the NMT initialization.
// To be sure, we assert those assumptions.

// Some shorts to save writing out the flags every time
static void* os_malloc(size_t s)              { return os::malloc(s, mtTest); }
static void* os_realloc(void* old, size_t s)  { return os::realloc(old, s, mtTest); }

static void log_state() {
  // Don't use tty! the only safe thing to use at all times is stringStream.
  char tmp[256];
  stringStream ss(tmp, sizeof(tmp));
  NMTPreInit::print_state(&ss);
  LOG("%s", tmp);
}

class TestAllocations {
  void* p1, *p2, *p3, *p4;
public:
  TestAllocations() {
    test_pre();
  }
  void test_pre() {
    // Note that this part will run every time a gtestlauncher execs (so, for every TEST_OTHER_VM).
    assert(!MemTracker::is_initialized(),
           "This should be run in pre-init phase (as part of C++ dyn. initialization)");
    LOG("corner cases, pre-init (%d)", os::current_process_id());
    log_state();

    p1 = os_malloc(100);                 // normal allocation
    os::free(os_malloc(0));              // 0-sized allocation, should be free-able
    p2 = os_realloc(os_malloc(10), 20);  // realloc, growing
    p3 = os_realloc(os_malloc(20), 10);  // realloc, shrinking
    p4 = os_realloc(nullptr, 10);        // realloc with null pointer
    os_realloc(os_realloc(os_malloc(20), 0), 30);  // realloc to size 0 and back up again
    os::free(os_malloc(20));             // malloc, free
    os::free(os_realloc(os_malloc(20), 30));  // malloc, realloc, free
    os::free(nullptr);                      // free(null)
    DEBUG_ONLY(NMTPreInit::verify();)

    // This should result in a fatal native oom error from NMT preinit with a clear error message.
    // It should not result in a SEGV or anything similar. Unfortunately difficult to test
    // automatically.
    // Uncomment to test manually.

    // case 1: overflow
    // os_malloc(SIZE_MAX);

    // case 2: failing malloc
    // os_malloc(SIZE_MAX - M);

    // case 3: overflow in realloc
    // void* p = os_malloc(10);
    // p = os_realloc(p, SIZE_MAX);

    // case 4: failing realloc
    // void* p = os_malloc(10);
    // p = os_realloc(p, SIZE_MAX - M);

    log_state();
  }
  void test_post() {
    assert(MemTracker::is_initialized(),
           "This should be run in post-init phase (from inside a TEST_VM test)");
    LOG("corner cases, post-init (%d)", os::current_process_id());
    log_state();

    p1 = os_realloc(p1, 140);  // realloc from pre-init-phase, growing
    p2 = os_realloc(p2, 150);  // realloc from pre-init-phase, growing
    p3 = os_realloc(p3, 50);   // realloc from pre-init-phase, growing
    p4 = os_realloc(p4, 8);    // realloc from pre-init-phase, shrinking
    DEBUG_ONLY(NMTPreInit::verify();)

    log_state();
  }
  void free_all() {
    assert(MemTracker::is_initialized(),
           "This should be run in post-init phase (from inside a TEST_VM test)");
    LOG("corner cases, free-all (%d)", os::current_process_id());
    log_state();

    os::free(p1); os::free(p2); os::free(p3); os::free(p4);
    DEBUG_ONLY(NMTPreInit::verify();)

    log_state();
  }
};

static TestAllocations g_test_allocations;

TEST_VM(NMTPreInit, pre_to_post_allocs) {
  g_test_allocations.test_post();
  g_test_allocations.free_all();
}
