/*
 * Copyright (c) 2019, 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 "runtime/interfaceSupport.inline.hpp"
#include "runtime/javaThread.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/threads.hpp"
#include "runtime/vmOperations.hpp"
#include "runtime/vmThread.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/ostream.hpp"
#include "unittest.hpp"

struct Threads::Test : public AllStatic {
  class VM_TestClaimOverflow;
  class CountThreads;
  class CheckClaims;
};

class Threads::Test::CountThreads : public ThreadClosure {
  uintx _claim_token;
  uint _java_threads_count;
  uint _non_java_threads_count;
  bool _need_claim;

public:
  CountThreads(uintx claim_token, bool need_claim) :
    _claim_token(claim_token),
    _java_threads_count(0),
    _non_java_threads_count(0),
    _need_claim(need_claim)
  {}

  virtual void do_thread(Thread* t) {
    if (!_need_claim || t->claim_threads_do(true, _claim_token)) {
      if (t->is_Java_thread()) {
        ++_java_threads_count;
      } else {
        ++_non_java_threads_count;
      }
    }
  }

  uint java_threads_count() const { return _java_threads_count; }
  uint non_java_threads_count() const { return _non_java_threads_count; }
  uint count() const { return _java_threads_count + _non_java_threads_count; }
};

class Threads::Test::CheckClaims : public ThreadClosure {
  uintx _claim_token;
  uint _java_threads_claimed;
  uint _java_threads_unclaimed;
  uint _non_java_threads_claimed;
  uint _non_java_threads_unclaimed;

public:
  CheckClaims(uintx claim_token) :
    _claim_token(claim_token),
    _java_threads_claimed(0),
    _java_threads_unclaimed(0),
    _non_java_threads_claimed(0),
    _non_java_threads_unclaimed(0)
  {}

  virtual void do_thread(Thread* t) {
    uintx thread_token = t->threads_do_token();
    if (thread_token == _claim_token) {
      if (t->is_Java_thread()) {
        ++_java_threads_claimed;
      } else {
        ++_non_java_threads_claimed;
      }
    } else {
      if (t->is_Java_thread()) {
        ++_java_threads_unclaimed;
      } else {
        ++_non_java_threads_unclaimed;
      }
    }
  }

  uint java_threads_claimed() const { return _java_threads_claimed; }
  uint java_threads_unclaimed() const { return _java_threads_unclaimed; }

  uint non_java_threads_claimed() const { return _non_java_threads_claimed; }
  uint non_java_threads_unclaimed() const { return _non_java_threads_unclaimed; }

  uint claimed() const {
    return _java_threads_claimed + _non_java_threads_claimed;
  }

  uint unclaimed() const {
    return _java_threads_unclaimed + _non_java_threads_unclaimed;
  }
};

class Threads::Test::VM_TestClaimOverflow : public VM_GTestExecuteAtSafepoint {
public:
  void doit() {
    // Prevent changes to the NJT list while we're conducting our test.
    MutexLocker ml(NonJavaThreadsList_lock, Mutex::_no_safepoint_check_flag);

    _thread_claim_token = max_uintx - 1;

    ASSERT_EQ(max_uintx - 1, thread_claim_token());
    CountThreads count1(thread_claim_token(), true);
    threads_do(&count1);
    tty->print_cr("Testing claim overflow with %u threads", count1.count());
    // At least the main thread and the VM thread.
    ASSERT_LE(2u, count1.count());
    ASSERT_LE(1u, count1.java_threads_count());
    ASSERT_LE(1u, count1.non_java_threads_count());

    ASSERT_EQ(max_uintx - 1, thread_claim_token());
    CheckClaims check1(thread_claim_token());
    threads_do(&check1);
    ASSERT_EQ(count1.count(), check1.claimed());
    ASSERT_EQ(count1.java_threads_count(), check1.java_threads_claimed());
    ASSERT_EQ(0u, check1.java_threads_unclaimed());
    ASSERT_EQ(count1.non_java_threads_count(), check1.non_java_threads_claimed());
    ASSERT_EQ(0u, check1.non_java_threads_unclaimed());

    change_thread_claim_token(); // No overflow yet.
    ASSERT_EQ(max_uintx, thread_claim_token());

    CountThreads count2(thread_claim_token(), false); // Claimed by PPTD below
    possibly_parallel_threads_do(true /* is_par */, &count2);
    ASSERT_EQ(count1.java_threads_count(), count2.java_threads_count());
    ASSERT_EQ(count1.non_java_threads_count(), count2.non_java_threads_count());

    CheckClaims check2(thread_claim_token());
    threads_do(&check2);
    ASSERT_EQ(count2.java_threads_count(), check2.java_threads_claimed());
    ASSERT_EQ(0u, check2.java_threads_unclaimed());
    ASSERT_EQ(0u, check2.non_java_threads_unclaimed());
    ASSERT_EQ(count1.non_java_threads_count(),
              check2.non_java_threads_claimed());

    change_thread_claim_token(); // Expect overflow.
    ASSERT_EQ(uintx(1), thread_claim_token());

    // Verify all threads have claim value of 0 after change overflow.
    CheckClaims check3(0);
    threads_do(&check3);
    ASSERT_EQ(count1.count(), check3.claimed());
    ASSERT_EQ(0u, check3.unclaimed());
  }
};

// Test overflow handling in Threads::change_thread_claim_token().
TEST_VM(ThreadsTest, claim_overflow) {
  Threads::Test::VM_TestClaimOverflow op;
  ThreadInVMfromNative invm(JavaThread::current());
  VMThread::execute(&op);
}

TEST_VM(ThreadsTest, fast_jni_in_vm) {
  JavaThread* current = JavaThread::current();
  JNIEnv* env = current->jni_environment();
  MACOS_AARCH64_ONLY(ThreadWXEnable wx(WXWrite, current));

  // DirectByteBuffer is an easy way to trigger GetIntField,
  // see JDK-8262896
  jlong capacity = 0x10000;
  jobject buffer = env->NewDirectByteBuffer(nullptr, (jlong)capacity);
  ASSERT_NE((void*)nullptr, buffer);
  ASSERT_EQ(capacity, env->GetDirectBufferCapacity(buffer));
}
