//===-- ScriptInterpreterPythonImpl.h ---------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "lldb/Host/Config.h"

#if LLDB_ENABLE_PYTHON

#include "lldb-python.h"

#include "PythonDataObjects.h"
#include "ScriptInterpreterPython.h"

#include "lldb/Host/Terminal.h"
#include "lldb/Utility/StreamString.h"

#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"

namespace lldb_private {
class IOHandlerPythonInterpreter;
class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
public:
  friend class IOHandlerPythonInterpreter;

  ScriptInterpreterPythonImpl(Debugger &debugger);

  ~ScriptInterpreterPythonImpl() override;

  bool Interrupt() override;

  bool ExecuteOneLine(
      llvm::StringRef command, CommandReturnObject *result,
      const ExecuteScriptOptions &options = ExecuteScriptOptions()) override;

  void ExecuteInterpreterLoop() override;

  bool ExecuteOneLineWithReturn(
      llvm::StringRef in_string,
      ScriptInterpreter::ScriptReturnType return_type, void *ret_value,
      const ExecuteScriptOptions &options = ExecuteScriptOptions()) override;

  lldb_private::Status ExecuteMultipleLines(
      const char *in_string,
      const ExecuteScriptOptions &options = ExecuteScriptOptions()) override;

  Status
  ExportFunctionDefinitionToInterpreter(StringList &function_def) override;

  bool GenerateTypeScriptFunction(StringList &input, std::string &output,
                                  const void *name_token = nullptr) override;

  bool GenerateTypeSynthClass(StringList &input, std::string &output,
                              const void *name_token = nullptr) override;

  bool GenerateTypeSynthClass(const char *oneliner, std::string &output,
                              const void *name_token = nullptr) override;

  // use this if the function code is just a one-liner script
  bool GenerateTypeScriptFunction(const char *oneliner, std::string &output,
                                  const void *name_token = nullptr) override;

  bool GenerateScriptAliasFunction(StringList &input,
                                   std::string &output) override;

  StructuredData::ObjectSP
  CreateSyntheticScriptedProvider(const char *class_name,
                                  lldb::ValueObjectSP valobj) override;

  StructuredData::GenericSP
  CreateScriptCommandObject(const char *class_name) override;

  StructuredData::ObjectSP
  CreateScriptedThreadPlan(const char *class_name,
                           StructuredDataImpl *args_data,
                           std::string &error_str,
                           lldb::ThreadPlanSP thread_plan) override;

  bool ScriptedThreadPlanExplainsStop(StructuredData::ObjectSP implementor_sp,
                                      Event *event,
                                      bool &script_error) override;

  bool ScriptedThreadPlanShouldStop(StructuredData::ObjectSP implementor_sp,
                                    Event *event, bool &script_error) override;

  bool ScriptedThreadPlanIsStale(StructuredData::ObjectSP implementor_sp,
                                 bool &script_error) override;

  lldb::StateType
  ScriptedThreadPlanGetRunState(StructuredData::ObjectSP implementor_sp,
                                bool &script_error) override;

  StructuredData::GenericSP
  CreateScriptedBreakpointResolver(const char *class_name,
                                   StructuredDataImpl *args_data,
                                   lldb::BreakpointSP &bkpt_sp) override;
  bool ScriptedBreakpointResolverSearchCallback(
      StructuredData::GenericSP implementor_sp,
      SymbolContext *sym_ctx) override;

  lldb::SearchDepth ScriptedBreakpointResolverSearchDepth(
      StructuredData::GenericSP implementor_sp) override;

  StructuredData::GenericSP
  CreateFrameRecognizer(const char *class_name) override;

  lldb::ValueObjectListSP
  GetRecognizedArguments(const StructuredData::ObjectSP &implementor,
                         lldb::StackFrameSP frame_sp) override;

  StructuredData::GenericSP
  OSPlugin_CreatePluginObject(const char *class_name,
                              lldb::ProcessSP process_sp) override;

  StructuredData::DictionarySP
  OSPlugin_RegisterInfo(StructuredData::ObjectSP os_plugin_object_sp) override;

  StructuredData::ArraySP
  OSPlugin_ThreadsInfo(StructuredData::ObjectSP os_plugin_object_sp) override;

  StructuredData::StringSP
  OSPlugin_RegisterContextData(StructuredData::ObjectSP os_plugin_object_sp,
                               lldb::tid_t thread_id) override;

  StructuredData::DictionarySP
  OSPlugin_CreateThread(StructuredData::ObjectSP os_plugin_object_sp,
                        lldb::tid_t tid, lldb::addr_t context) override;

  StructuredData::ObjectSP
  LoadPluginModule(const FileSpec &file_spec,
                   lldb_private::Status &error) override;

  StructuredData::DictionarySP
  GetDynamicSettings(StructuredData::ObjectSP plugin_module_sp, Target *target,
                     const char *setting_name,
                     lldb_private::Status &error) override;

  size_t CalculateNumChildren(const StructuredData::ObjectSP &implementor,
                              uint32_t max) override;

  lldb::ValueObjectSP
  GetChildAtIndex(const StructuredData::ObjectSP &implementor,
                  uint32_t idx) override;

  int GetIndexOfChildWithName(const StructuredData::ObjectSP &implementor,
                              const char *child_name) override;

  bool UpdateSynthProviderInstance(
      const StructuredData::ObjectSP &implementor) override;

  bool MightHaveChildrenSynthProviderInstance(
      const StructuredData::ObjectSP &implementor) override;

  lldb::ValueObjectSP
  GetSyntheticValue(const StructuredData::ObjectSP &implementor) override;

  ConstString
  GetSyntheticTypeName(const StructuredData::ObjectSP &implementor) override;

  bool
  RunScriptBasedCommand(const char *impl_function, llvm::StringRef args,
                        ScriptedCommandSynchronicity synchronicity,
                        lldb_private::CommandReturnObject &cmd_retobj,
                        Status &error,
                        const lldb_private::ExecutionContext &exe_ctx) override;

  bool RunScriptBasedCommand(
      StructuredData::GenericSP impl_obj_sp, llvm::StringRef args,
      ScriptedCommandSynchronicity synchronicity,
      lldb_private::CommandReturnObject &cmd_retobj, Status &error,
      const lldb_private::ExecutionContext &exe_ctx) override;

  Status GenerateFunction(const char *signature,
                          const StringList &input) override;

  Status GenerateBreakpointCommandCallbackData(
      StringList &input,
      std::string &output,
      bool has_extra_args) override;

  bool GenerateWatchpointCommandCallbackData(StringList &input,
                                             std::string &output) override;

  bool GetScriptedSummary(const char *function_name, lldb::ValueObjectSP valobj,
                          StructuredData::ObjectSP &callee_wrapper_sp,
                          const TypeSummaryOptions &options,
                          std::string &retval) override;

  bool GetDocumentationForItem(const char *item, std::string &dest) override;

  bool GetShortHelpForCommandObject(StructuredData::GenericSP cmd_obj_sp,
                                    std::string &dest) override;

  uint32_t
  GetFlagsForCommandObject(StructuredData::GenericSP cmd_obj_sp) override;

  bool GetLongHelpForCommandObject(StructuredData::GenericSP cmd_obj_sp,
                                   std::string &dest) override;

  bool CheckObjectExists(const char *name) override {
    if (!name || !name[0])
      return false;
    std::string temp;
    return GetDocumentationForItem(name, temp);
  }

  bool RunScriptFormatKeyword(const char *impl_function, Process *process,
                              std::string &output, Status &error) override;

  bool RunScriptFormatKeyword(const char *impl_function, Thread *thread,
                              std::string &output, Status &error) override;

  bool RunScriptFormatKeyword(const char *impl_function, Target *target,
                              std::string &output, Status &error) override;

  bool RunScriptFormatKeyword(const char *impl_function, StackFrame *frame,
                              std::string &output, Status &error) override;

  bool RunScriptFormatKeyword(const char *impl_function, ValueObject *value,
                              std::string &output, Status &error) override;

  bool
  LoadScriptingModule(const char *filename, bool init_session,
                      lldb_private::Status &error,
                      StructuredData::ObjectSP *module_sp = nullptr) override;

  bool IsReservedWord(const char *word) override;

  std::unique_ptr<ScriptInterpreterLocker> AcquireInterpreterLock() override;

  void CollectDataForBreakpointCommandCallback(
      std::vector<BreakpointOptions *> &bp_options_vec,
      CommandReturnObject &result) override;

  void
  CollectDataForWatchpointCommandCallback(WatchpointOptions *wp_options,
                                          CommandReturnObject &result) override;

  /// Set the callback body text into the callback for the breakpoint.
  Status SetBreakpointCommandCallback(BreakpointOptions *bp_options,
                                      const char *callback_body) override;

  Status SetBreakpointCommandCallbackFunction(
      BreakpointOptions *bp_options,
      const char *function_name,
      StructuredData::ObjectSP extra_args_sp) override;

  /// This one is for deserialization:
  Status SetBreakpointCommandCallback(
      BreakpointOptions *bp_options,
      std::unique_ptr<BreakpointOptions::CommandData> &data_up) override;

  Status SetBreakpointCommandCallback(BreakpointOptions *bp_options,
                                      const char *command_body_text,
                                      StructuredData::ObjectSP extra_args_sp,
                                      bool uses_extra_args);

  /// Set a one-liner as the callback for the watchpoint.
  void SetWatchpointCommandCallback(WatchpointOptions *wp_options,
                                    const char *oneliner) override;

  const char *GetDictionaryName() { return m_dictionary_name.c_str(); }

  PyThreadState *GetThreadState() { return m_command_thread_state; }

  void SetThreadState(PyThreadState *s) {
    if (s)
      m_command_thread_state = s;
  }

  // IOHandlerDelegate
  void IOHandlerActivated(IOHandler &io_handler, bool interactive) override;

  void IOHandlerInputComplete(IOHandler &io_handler,
                              std::string &data) override;

  static lldb::ScriptInterpreterSP CreateInstance(Debugger &debugger);

  // PluginInterface protocol
  lldb_private::ConstString GetPluginName() override;

  uint32_t GetPluginVersion() override;

  class Locker : public ScriptInterpreterLocker {
  public:
    enum OnEntry {
      AcquireLock = 0x0001,
      InitSession = 0x0002,
      InitGlobals = 0x0004,
      NoSTDIN = 0x0008
    };

    enum OnLeave {
      FreeLock = 0x0001,
      FreeAcquiredLock = 0x0002, // do not free the lock if we already held it
                                 // when calling constructor
      TearDownSession = 0x0004
    };

    Locker(ScriptInterpreterPythonImpl *py_interpreter,
           uint16_t on_entry = AcquireLock | InitSession,
           uint16_t on_leave = FreeLock | TearDownSession,
           lldb::FileSP in = nullptr, lldb::FileSP out = nullptr,
           lldb::FileSP err = nullptr);

    ~Locker() override;

  private:
    bool DoAcquireLock();

    bool DoInitSession(uint16_t on_entry_flags, lldb::FileSP in,
                       lldb::FileSP out, lldb::FileSP err);

    bool DoFreeLock();

    bool DoTearDownSession();

    bool m_teardown_session;
    ScriptInterpreterPythonImpl *m_python_interpreter;
    PyGILState_STATE m_GILState;
  };

  static bool BreakpointCallbackFunction(void *baton,
                                         StoppointCallbackContext *context,
                                         lldb::user_id_t break_id,
                                         lldb::user_id_t break_loc_id);
  static bool WatchpointCallbackFunction(void *baton,
                                         StoppointCallbackContext *context,
                                         lldb::user_id_t watch_id);
  static void InitializePrivate();

  class SynchronicityHandler {
  private:
    lldb::DebuggerSP m_debugger_sp;
    ScriptedCommandSynchronicity m_synch_wanted;
    bool m_old_asynch;

  public:
    SynchronicityHandler(lldb::DebuggerSP, ScriptedCommandSynchronicity);

    ~SynchronicityHandler();
  };

  enum class AddLocation { Beginning, End };

  static void AddToSysPath(AddLocation location, std::string path);

  bool EnterSession(uint16_t on_entry_flags, lldb::FileSP in, lldb::FileSP out,
                    lldb::FileSP err);

  void LeaveSession();

  uint32_t IsExecutingPython() const { return m_lock_count > 0; }

  uint32_t IncrementLockCount() { return ++m_lock_count; }

  uint32_t DecrementLockCount() {
    if (m_lock_count > 0)
      --m_lock_count;
    return m_lock_count;
  }

  enum ActiveIOHandler {
    eIOHandlerNone,
    eIOHandlerBreakpoint,
    eIOHandlerWatchpoint
  };

  python::PythonModule &GetMainModule();

  python::PythonDictionary &GetSessionDictionary();

  python::PythonDictionary &GetSysModuleDictionary();

  llvm::Expected<unsigned> GetMaxPositionalArgumentsForCallable(
      const llvm::StringRef &callable_name) override;

  bool GetEmbeddedInterpreterModuleObjects();

  bool SetStdHandle(lldb::FileSP file, const char *py_name,
                    python::PythonObject &save_file, const char *mode);

  python::PythonObject m_saved_stdin;
  python::PythonObject m_saved_stdout;
  python::PythonObject m_saved_stderr;
  python::PythonModule m_main_module;
  python::PythonDictionary m_session_dict;
  python::PythonDictionary m_sys_module_dict;
  python::PythonObject m_run_one_line_function;
  python::PythonObject m_run_one_line_str_global;
  std::string m_dictionary_name;
  ActiveIOHandler m_active_io_handler;
  bool m_session_is_active;
  bool m_pty_slave_is_open;
  bool m_valid_session;
  uint32_t m_lock_count;
  PyThreadState *m_command_thread_state;
};

class IOHandlerPythonInterpreter : public IOHandler {
public:
  IOHandlerPythonInterpreter(Debugger &debugger,
                             ScriptInterpreterPythonImpl *python)
      : IOHandler(debugger, IOHandler::Type::PythonInterpreter),
        m_python(python) {}

  ~IOHandlerPythonInterpreter() override {}

  ConstString GetControlSequence(char ch) override {
    if (ch == 'd')
      return ConstString("quit()\n");
    return ConstString();
  }

  void Run() override {
    if (m_python) {
      int stdin_fd = GetInputFD();
      if (stdin_fd >= 0) {
        Terminal terminal(stdin_fd);
        TerminalState terminal_state;
        const bool is_a_tty = terminal.IsATerminal();

        if (is_a_tty) {
          terminal_state.Save(stdin_fd, false);
          terminal.SetCanonical(false);
          terminal.SetEcho(true);
        }

        ScriptInterpreterPythonImpl::Locker locker(
            m_python,
            ScriptInterpreterPythonImpl::Locker::AcquireLock |
                ScriptInterpreterPythonImpl::Locker::InitSession |
                ScriptInterpreterPythonImpl::Locker::InitGlobals,
            ScriptInterpreterPythonImpl::Locker::FreeAcquiredLock |
                ScriptInterpreterPythonImpl::Locker::TearDownSession);

        // The following call drops into the embedded interpreter loop and
        // stays there until the user chooses to exit from the Python
        // interpreter. This embedded interpreter will, as any Python code that
        // performs I/O, unlock the GIL before a system call that can hang, and
        // lock it when the syscall has returned.

        // We need to surround the call to the embedded interpreter with calls
        // to PyGILState_Ensure and PyGILState_Release (using the Locker
        // above). This is because Python has a global lock which must be held
        // whenever we want to touch any Python objects. Otherwise, if the user
        // calls Python code, the interpreter state will be off, and things
        // could hang (it's happened before).

        StreamString run_string;
        run_string.Printf("run_python_interpreter (%s)",
                          m_python->GetDictionaryName());
        PyRun_SimpleString(run_string.GetData());

        if (is_a_tty)
          terminal_state.Restore();
      }
    }
    SetIsDone(true);
  }

  void Cancel() override {}

  bool Interrupt() override { return m_python->Interrupt(); }

  void GotEOF() override {}

protected:
  ScriptInterpreterPythonImpl *m_python;
};

} // namespace lldb_private

#endif