/*
 * Copyright (c) 2022, 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 "ci/ciKlass.hpp"
#include "ci/ciMethod.hpp"
#include "classfile/vmSymbols.hpp"
#include "interpreter/linkResolver.hpp"
#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdMacros.hpp"
#include "jfr/recorder/jfrRecorder.hpp"
#include "jfr/recorder/stacktrace/jfrStackTrace.hpp"
#include "jfr/support/jfrDeprecationManager.hpp"
#include "jfr/support/jfrResolution.hpp"
#include "memory/resourceArea.inline.hpp"
#include "oops/method.inline.hpp"
#include "runtime/javaThread.inline.hpp"
#include "runtime/vframe.inline.hpp"
#ifdef COMPILER1
#include "c1/c1_GraphBuilder.hpp"
#endif
#ifdef COMPILER2
#include "opto/parse.hpp"
#endif

#include <string.h>

// The following packages are internal implmentation details used by reflection.
// We exclude matching frames on the stack in a manner similar to StackWalker.
static constexpr const int NUM_EXCLUDED_PACKAGES = 4;
static constexpr const char* excluded_packages[NUM_EXCLUDED_PACKAGES] = { "java/lang/invoke/",
                                                                          "jdk/internal/reflect/",
                                                                          "java/lang/reflect/",
                                                                          "sun/invoke/" };

static inline bool match(const char* str, const char* sub_str) {
  assert(str != nullptr, "invariant");
  assert(sub_str != nullptr, "invariant");
  return strstr(str, sub_str) == str;
}

// Caller requires ResourceMark.
static inline bool exclude_frame(const Method* method) {
  assert(method != nullptr, "invariant");
  // exclude native methods.
  if (method->is_native()) {
    return true;
  }
  const Klass* const klass = method->method_holder();
  assert(klass != nullptr, "invariant");
  const Symbol* const klass_sym = klass->name();
  assert(klass_sym != nullptr, "invariant");
  const char* const klass_name = klass_sym->as_C_string();
  assert(klass_name != nullptr, "invariant");
  for (int i = 0; i < NUM_EXCLUDED_PACKAGES; ++i) {
    if (match(klass_name,excluded_packages[i])) {
      return true;
    }
  }
  return false;
}

static Method* find_real_sender(vframeStream& stream, JavaThread* jt) {
  assert(jt != nullptr, "invariant");
  assert(stream.method()->is_native(), "invariant");
  ResourceMark rm(jt);
  while (!stream.at_end()) {
    stream.next();
    Method* method = stream.method();
    if (!exclude_frame(method)) {
      return method;
    }
  }
  return nullptr;
}

static inline bool jfr_is_started_on_command_line() {
  return JfrRecorder::is_started_on_commandline();
}

static inline Method* frame_context(vframeStream& stream, int& bci, u1& frame_type, JavaThread* jt) {
  Method* method = stream.method();
  assert(method != nullptr, "invariant");
  if (method->is_native()) {
    method = find_real_sender(stream, jt);
    if (method == nullptr) {
      return nullptr;
    }
  }
  assert(method != nullptr, "invariant");
  assert(!method->is_native(), "invariant");
  bci = stream.bci();
  frame_type = stream.is_interpreted_frame() ? JfrStackFrame::FRAME_INTERPRETER : JfrStackFrame::FRAME_JIT;
  if (frame_type == JfrStackFrame::FRAME_JIT && !stream.at_end()) {
    const intptr_t* const id = stream.frame_id();
    stream.next();
    if (id == stream.frame_id()) {
      frame_type = JfrStackFrame::FRAME_INLINE;
    }
  }
  return method;
}

static inline Method* ljf_sender_method(int& bci, u1& frame_type, JavaThread* jt) {
  assert(jt != nullptr, "invariant");
  if (!jt->has_last_Java_frame()) {
    return nullptr;
  }
  vframeStream stream(jt, true, false);
  return frame_context(stream, bci, frame_type, jt);
}

static inline void on_runtime_deprecated(const Method* method, JavaThread* jt) {
  assert(jt != nullptr, "invariant");
  assert(method != nullptr, "invariant");
  assert(method->deprecated(), "invariant");
  if (jfr_is_started_on_command_line()) {
    int bci;
    u1 frame_type;
    Method* const sender = ljf_sender_method(bci, frame_type, jt);
    if (sender != nullptr) {
      JfrDeprecationManager::on_link(method, sender, bci, frame_type, jt);
    }
  }
}

// We can circumvent the need to hook into backpatching if ciMethod is made aware
// of the deprecated annotation already as part of parsing bytecodes of the callee method.
static void on_backpatching_deprecated(const Method* deprecated_method, JavaThread* jt) {
  assert(deprecated_method != nullptr, "invariant");
  assert(deprecated_method->deprecated(), "invariant");
  assert(jt->has_last_Java_frame(), "invariant");
  assert(jt->last_frame().is_runtime_frame(), "invariant");
  if (jfr_is_started_on_command_line()) {
    vframeStream stream(jt, true, false);
    assert(!stream.at_end(), "invariant");
    stream.next(); // now at caller
    int bci;
    u1 frame_type;
    Method* const sender = frame_context(stream, bci, frame_type, jt);
    if (sender != nullptr) {
      JfrDeprecationManager::on_link(deprecated_method, sender, bci, frame_type, jt);
    }
  }
}

void JfrResolution::on_backpatching(const Method* callee_method, JavaThread* jt) {
  assert(callee_method != nullptr, "invariant");
  assert(jt != nullptr, "invariant");
  if (callee_method->deprecated()) {
    on_backpatching_deprecated(callee_method, jt);
  }
}

static inline const Method* ljf_sender_method(JavaThread* jt) {
  assert(jt != nullptr, "invariant");
  if (!jt->has_last_Java_frame()) {
    return nullptr;
  }
  const vframeStream ljf(jt, true, false);
  return ljf.method();
}

static const char* const link_error_msg = "illegal access linking method 'jdk.jfr.internal.event.EventWriter.getEventWriter()'";

void JfrResolution::on_runtime_resolution(const CallInfo & info, TRAPS) {
  assert(info.selected_method() != nullptr, "invariant");
  assert(info.resolved_klass() != nullptr, "invariant");
  static const Symbol* const event_writer_method_name = vmSymbols::getEventWriter_name();
  assert(event_writer_method_name != nullptr, "invariant");
  Method* const method = info.selected_method();
  assert(method != nullptr, "invariant");
  if (method->deprecated()) {
    on_runtime_deprecated(method, THREAD);
    return;
  }
  // Fast path
  if (method->name() != event_writer_method_name) {
    return;
  }
  static const Symbol* const event_writer_klass_name = vmSymbols::jdk_jfr_internal_event_EventWriter();
  assert(event_writer_klass_name != nullptr, "invariant");
  if (info.resolved_klass()->name() != event_writer_klass_name) {
    return;
  }
  // Attempting to link against jdk.jfr.internal.event.EventWriter.getEventWriter().
  // The sender, i.e. the method attempting to link, is in the ljf (if one exists).
  const Method* const sender = ljf_sender_method(THREAD);
  if (sender == nullptr) {
    // A compiler thread is doing linktime resolution but there is no information about the sender available.
    // For the compiler threads, the sender is instead found as part of bytecode parsing.
    return;
  }
  // Is the sender method blessed for linkage?
  if (IS_METHOD_BLESSED(sender)) {
    return;
  }
#if INCLUDE_JVMCI
  // JVMCI compiler is doing linktime resolution
  if (sender->method_holder()->name() == vmSymbols::jdk_vm_ci_hotspot_CompilerToVM()) {
    if (sender->name()->equals("lookupMethodInPool")) {
      return;
    }
  }
#endif
  THROW_MSG(vmSymbols::java_lang_IllegalAccessError(), link_error_msg);
}

static inline bool is_compiler_linking_event_writer(const Symbol* holder, const Symbol* name) {
  static const Symbol* const event_writer_klass_name = vmSymbols::jdk_jfr_internal_event_EventWriter();
  assert(event_writer_klass_name != nullptr, "invariant");
  if (holder != event_writer_klass_name) {
    return false;
  }
  static const Symbol* const event_writer_method_name = vmSymbols::getEventWriter_name();
  assert(event_writer_method_name != nullptr, "invariant");
  return name == event_writer_method_name;
}

static inline bool is_compiler_linking_event_writer(const ciKlass * holder, const ciMethod * target) {
  assert(holder != nullptr, "invariant");
  assert(target != nullptr, "invariant");
  return is_compiler_linking_event_writer(holder->name()->get_symbol(), target->name()->get_symbol());
}

static inline void on_compiler_resolve_deprecated(const ciMethod* target, int bci, Method* sender) {
  assert(target != nullptr, "invariant");
  assert(sender != nullptr, "invariant");
  if (jfr_is_started_on_command_line()) {
    const Method* const method = target->get_Method();
    assert(method != nullptr, "Invariant");
    assert(method->deprecated(), "invariant");
    JfrDeprecationManager::on_link(method, sender, bci, JfrStackFrame::FRAME_JIT, JavaThread::current());
  }
}

#ifdef COMPILER1
// C1
void JfrResolution::on_c1_resolution(const GraphBuilder * builder, const ciKlass * holder, const ciMethod * target) {
  Method* const sender = builder->method()->get_Method();
  if (is_compiler_linking_event_writer(holder, target) && !IS_METHOD_BLESSED(sender)) {
    builder->bailout(link_error_msg);
    return;
  }
  if (target->deprecated()) {
    on_compiler_resolve_deprecated(target, builder->bci(), sender);
  }
}
#endif

#ifdef COMPILER2
// C2
void JfrResolution::on_c2_resolution(const Parse * parse, const ciKlass * holder, const ciMethod * target) {
  Method* const sender = parse->method()->get_Method();
  if (is_compiler_linking_event_writer(holder, target) && !IS_METHOD_BLESSED(sender)) {
    parse->C->record_failure(link_error_msg);
    return;
  }
  if (target->deprecated()) {
    on_compiler_resolve_deprecated(target, parse->bci(), sender);
  }
}
#endif

#if INCLUDE_JVMCI
// JVMCI
void JfrResolution::on_jvmci_resolution(const Method* caller, const Method* target, TRAPS) {
  if (is_compiler_linking_event_writer(target->method_holder()->name(), target->name())) {
    if (caller == nullptr || !IS_METHOD_BLESSED(caller)) {
      THROW_MSG(vmSymbols::java_lang_IllegalAccessError(), link_error_msg);
    }
  }
}
#endif
