/*
 * Copyright (c) 2023, 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 "cds/cdsConfig.hpp"
#include "cds/cds_globals.hpp"
#include "logging/log.hpp"
#include "memory/universe.hpp"
#include "prims/jvmtiAgentList.hpp"
#include "prims/jvmtiEnvBase.hpp"
#include "prims/jvmtiExport.hpp"
#include "runtime/atomic.hpp"
#include "runtime/os.inline.hpp"

JvmtiAgent* JvmtiAgentList::_list = nullptr;

// Selection as a function of the filter.
JvmtiAgent* JvmtiAgentList::Iterator::select(JvmtiAgent* agent) const {
  while (agent != nullptr) {
    if (_filter == ALL) {
      return agent;
    } else if (_filter == NOT_XRUN) {
      if (!agent->is_xrun()) {
        return agent;
      }
    } else if (_filter == JAVA) {
      if (agent->is_jplis()) {
        return agent;
      }
    } else if (_filter == NATIVE) {
      if (!agent->is_jplis() && !agent->is_xrun()) {
        return agent;
      }
    } else {
      assert(_filter == XRUN, "invariant");
      if (agent->is_xrun()) {
        return agent;
      }
    }
    agent = agent->next();
  }
  return nullptr;
}

static inline JvmtiAgent* head(JvmtiAgent** list) {
  assert(list != nullptr, "invariant");
  return Atomic::load_acquire(list);
}

// The storage list is a single cas-linked-list, to allow for concurrent iterations.
// Especially during initial loading of agents, there exist an order requirement to iterate oldest -> newest.
// Our concurrent storage linked-list is newest -> oldest.
// The correct order is preserved by the iterator, by storing a filtered set of entries in a stack.
JvmtiAgentList::Iterator::Iterator(JvmtiAgent** list, Filter filter) :
  _stack(new GrowableArrayCHeap<JvmtiAgent*, mtServiceability>(16)), _filter(filter) {
  JvmtiAgent* next = head(list);
  while (next != nullptr) {
    next = select(next);
    if (next != nullptr) {
      _stack->push(next);
      next = next->next();
    }
  }
}

bool JvmtiAgentList::Iterator::has_next() const {
  assert(_stack != nullptr, "invariant");
  return _stack->is_nonempty();
}

const JvmtiAgent* JvmtiAgentList::Iterator::next() const {
  assert(has_next(), "invariant");
  return _stack->pop();
}

JvmtiAgent* JvmtiAgentList::Iterator::next() {
  return const_cast<JvmtiAgent*>(const_cast<const Iterator*>(this)->next());
}

JvmtiAgentList::Iterator JvmtiAgentList::agents() {
  return Iterator(&_list, Iterator::NOT_XRUN);
}

JvmtiAgentList::Iterator JvmtiAgentList::java_agents() {
  return Iterator(&_list, Iterator::JAVA);
}

JvmtiAgentList::Iterator JvmtiAgentList::native_agents() {
  return Iterator(&_list, Iterator::NATIVE);
}

JvmtiAgentList::Iterator JvmtiAgentList::xrun_agents() {
  return Iterator(&_list, Iterator::XRUN);
}

JvmtiAgentList::Iterator JvmtiAgentList::all() {
  return Iterator(&_list, Iterator::ALL);
}

void JvmtiAgentList::add(JvmtiAgent* agent) {
  assert(agent != nullptr, "invariant");
  JvmtiAgent* next;
  do {
    next = head(&_list);
    agent->set_next(next);
  } while (Atomic::cmpxchg(&_list, next, agent) != next);
}

void JvmtiAgentList::add(const char* name, const char* options, bool absolute_path) {
  add(new JvmtiAgent(name, options, absolute_path));
}

void JvmtiAgentList::add_xrun(const char* name, const char* options, bool absolute_path) {
  JvmtiAgent* agent = new JvmtiAgent(name, options, absolute_path);
  agent->set_xrun();
  add(agent);
}

#ifdef ASSERT
static void assert_initialized(JvmtiAgentList::Iterator& it) {
  while (it.has_next()) {
    assert(it.next()->is_initialized(), "invariant");
  }
}
#endif

// In case an agent did not enable the VMInit callback, or if it is an -Xrun agent,
// it gets an initializiation timestamp here.
void JvmtiAgentList::initialize() {
  Iterator it = all();
  while (it.has_next()) {
    JvmtiAgent* agent = it.next();
    if (!agent->is_initialized()) {
      agent->initialization_begin();
    }
  }
  DEBUG_ONLY(Iterator assert_it = all(); assert_initialized(assert_it);)
}

void JvmtiAgentList::convert_xrun_agents() {
  Iterator it = xrun_agents();
  while (it.has_next()) {
    it.next()->convert_xrun_agent();
  }
}

class JvmtiPhaseTransition : public StackObj {
 public:
  JvmtiPhaseTransition() {
    assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_PRIMORDIAL, "invalid init sequence");
    JvmtiExport::enter_onload_phase();
  }
  ~JvmtiPhaseTransition() {
    assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_ONLOAD, "invariant");
    JvmtiExport::enter_primordial_phase();
  }
};

static void load_agents(JvmtiAgentList::Iterator& it) {
  while (it.has_next()) {
    it.next()->load();
  }
}

// Invokes Agent_OnLoad for -agentlib:.. -agentpath:  and converted -Xrun agents.
// Called very early -- before JavaThreads exist
void JvmtiAgentList::load_agents() {
  // Convert -Xrun to -agentlib: if there is no JVM_OnLoad
  convert_xrun_agents();
  JvmtiPhaseTransition transition;
  Iterator it = agents();
  ::load_agents(it);
}

// Launch -Xrun agents
void JvmtiAgentList::load_xrun_agents() {
  assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_PRIMORDIAL, "invalid init sequence");
  Iterator it = xrun_agents();
  ::load_agents(it);
}

// Invokes Agent_OnAttach for agents loaded dynamically during runtime.
void JvmtiAgentList::load_agent(const char* agent_name, bool is_absolute_path,
                                const char* options, outputStream* st) {
  JvmtiAgent* const agent = new JvmtiAgent(agent_name, options, is_absolute_path, /* dynamic agent */ true);
  if (agent->load(st)) {
    add(agent);
  } else {
    delete agent;
  }
}

// Send any Agent_OnUnload notifications
void JvmtiAgentList::unload_agents() {
  Iterator it = agents();
  while (it.has_next()) {
    it.next()->unload();
  }
}

// Return true if a statically linked agent is on the list
bool JvmtiAgentList::is_static_lib_loaded(const char* name) {
  JvmtiAgentList::Iterator it = JvmtiAgentList::agents();
  while (it.has_next()) {
    JvmtiAgent* const agent = it.next();
    if (agent->is_static_lib() && strcmp(agent->name(), name) == 0) {
      return true;
    }
  }
  return false;
}

// Return true if a agent library on the list
bool JvmtiAgentList::is_dynamic_lib_loaded(void* os_lib) {
  JvmtiAgentList::Iterator it = JvmtiAgentList::agents();
  while (it.has_next()) {
    JvmtiAgent* const agent = it.next();
    if (!agent->is_static_lib() && agent->os_lib() == os_lib) {
      return true;
    }
  }
  return false;
}

static bool match(JvmtiEnv* env, const JvmtiAgent* agent, const void* os_module_address) {
  assert(env != nullptr, "invariant");
  assert(agent != nullptr, "invariant");
  if (agent->is_static_lib()) {
    return os::get_default_process_handle() == os_module_address;
  }
  if (agent->os_lib() != os_module_address) {
    return false;
  }
  return agent->is_instrument_lib() ? agent->is_jplis(env) : true;
}

// The function pointer is a JVMTI callback function.
// Find the os module (dll) that exports this function.
// Now we can map a JVMTI env to its corresponding agent.
JvmtiAgent* JvmtiAgentList::lookup(JvmtiEnv* env, void* f_ptr) {
  assert(env != nullptr, "invariant");
  assert(f_ptr != nullptr, "invariant");
  static char buffer[JVM_MAXPATHLEN];
  int offset;
  if (!os::dll_address_to_library_name(reinterpret_cast<address>(f_ptr), &buffer[0], JVM_MAXPATHLEN, &offset)) {
    return nullptr;
  }
  assert(buffer[0] != '\0', "invariant");
  const void* const os_module_address = reinterpret_cast<address>(f_ptr) - offset;

  JvmtiAgentList::Iterator it = JvmtiAgentList::agents();
  while (it.has_next()) {
    JvmtiAgent* const agent = it.next();
    if (match(env, agent, os_module_address)) {
      agent->set_os_lib_path(&buffer[0]);
      return agent;
    }
  }
  return nullptr;
}

void JvmtiAgentList::disable_agent_list() {
#if INCLUDE_CDS
  assert(CDSConfig::is_dumping_final_static_archive(), "use this only for -XX:AOTMode=create!");
  assert(!Universe::is_bootstrapping() && !Universe::is_fully_initialized(), "must do this very early");
  log_info(aot)("Disabled all JVMTI agents during -XX:AOTMode=create");
  _list = nullptr; // Pretend that no agents have been added.
#endif
}
