<commit_before>/*
 * RStdCallbacks.cpp
 *
 * Copyright (C) 2009-18 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#define R_INTERNAL_FUNCTIONS

#include <boost/function.hpp>
#include <boost/regex.hpp>

#include <r/RExec.hpp>
#include <r/ROptions.hpp>
#include <r/RSourceManager.hpp>
#include <r/RUtil.hpp>
#include <r/session/RClientState.hpp>
#include <r/session/RConsoleActions.hpp>
#include <r/session/RConsoleHistory.hpp>
#include <r/session/RSession.hpp>
#include <r/session/RSessionState.hpp>

#include <core/FileSerializer.hpp>
#include <core/RegexUtils.hpp>

#include "RInit.hpp"
#include "REmbedded.hpp"
#include "RStdCallbacks.hpp"
#include "RQuit.hpp"
#include "RSuspend.hpp"

#include "graphics/RGraphicsDevDesc.hpp"
#include "graphics/RGraphicsUtils.hpp"
#include "graphics/RGraphicsDevice.hpp"
#include "graphics/RGraphicsPlotManager.hpp"

#include <Rembedded.h>

extern "C" {

#ifndef _WIN32
SA_TYPE SaveAction;
#else
__declspec(dllimport) SA_TYPE SaveAction;
#endif

}

using namespace rstudio::core;

namespace rstudio {
namespace r {
namespace session {

namespace {

// have we completed our one-time initialization yet?
bool s_initialized = false;

// main callbacks; pointer to statically allocated memory
RCallbacks s_callbacks;

// internal callbacks for delegation
InternalCallbacks s_internalCallbacks;

// temporarily suppress output
bool s_suppressOutput = false;

class JumpToTopException
{
};

FilePath rSaveGlobalEnvironmentFilePath()
{
   FilePath rEnvironmentDir = utils::rEnvironmentDir();
   return rEnvironmentDir.complete(".RData");
}

void rSuicideError(const Error& error)
{
   // provide error message if the error was unexpected
   std::string msg;
   if (!error.expected())
      msg = core::log::errorAsLogEntry(error);

   rSuicide(msg);
}

SA_TYPE saveAsk()
{
   try
   {
      // end user prompt
      std::string wsPath = FilePath::createAliasedPath(
            rSaveGlobalEnvironmentFilePath(), utils::userHomePath());
      std::string prompt = "Save workspace image to " + wsPath + "? [y/n";
      // The Rf_jump_to_top_level doesn't work (freezes the process) with
      // 64-bit mingw due to the way it does stack unwinding. Since this is
      // a farily obscure gesture (quit from command line then cancel the quit)
      // we just eliminate the possiblity of it on windows
#ifndef _WIN32
      prompt += "/c";
#endif
      prompt += "]: ";

      // input buffer
      std::vector<CONSOLE_BUFFER_CHAR> inputBuffer(512, 0);

      while(true)
      {
         // read input
         RReadConsole(prompt.c_str(), &(inputBuffer[0]), inputBuffer.size(), 0);
         std::string input(1, inputBuffer[0]);
         boost::algorithm::to_lower(input);

         // look for yes, no, or cancel
         if (input == "y")
            return SA_SAVE;
         else if (input == "n")
            return SA_NOSAVE;
#ifndef _WIN32
         else if (input == "c")
            throw JumpToTopException();
#endif
      }
   }
   catch(JumpToTopException)
   {
      Rf_jump_to_toplevel();
   }

   // keep compiler happy
   return SA_SAVE;
}

void doHistoryFileOperation(SEXP args, 
                            boost::function<Error(const FilePath&)> fileOp)
{
   // validate filename argument
   SEXP file = CAR(args);
   if (!sexp::isString(file) || sexp::length(file) < 1)
      throw r::exec::RErrorException("invalid 'file' argument");
   
   // calculate full path to history file
   FilePath historyFilePath(R_ExpandFileName(Rf_translateChar(STRING_ELT(file, 
                                                                         0))));
   // perform operation
   Error error = fileOp(historyFilePath);
   if (error)
      throw r::exec::RErrorException(error.code().message());
}
   
bool consoleInputHook(const std::string& prompt,
                      const std::string& input)
{
   // only check for quit when we're at the default prompt
   if (!r::session::utils::isDefaultPrompt(prompt))
      return true;

   // check for user quit invocation
    boost::regex re("^\\s*(q|quit)\\s*\\(.*$");
    boost::smatch match;
    if (regex_utils::match(input, match, re))
   {
      if (!s_callbacks.handleUnsavedChanges())
      {
         REprintf("User cancelled quit operation\n");
         return false;
      }

      // on win32 we will actually assume responsibility for the
      // quit function entirely (so we can call our internal cleanup
      // handler code)
#ifdef _WIN32
      std::string quitErr;
      bool didQuit = win32Quit(input, &quitErr);
      if (!didQuit)
         REprintf((quitErr + "\n").c_str());

      // always return false (since we take over the command fully)
      return false;
#else
      return true;
#endif
   }
   else
   {
      return true;
   }
}

bool isInjectedBrowserCommand(const std::string& cmd)
{
   return browserContextActive() &&
          (cmd == "c" || cmd == "Q" || cmd == "n" || cmd == "s" || cmd == "f");
}

Error saveDefaultGlobalEnvironment()
{
   // path to save to
   FilePath globalEnvPath = rSaveGlobalEnvironmentFilePath();

   // notify client of serialization status
   SerializationCallbackScope cb(kSerializationActionSaveDefaultWorkspace,
                                 globalEnvPath);

   // suppress interrupts which occur during saving
   r::exec::IgnoreInterruptsScope ignoreInterrupts;
         
   // save global environment
   std::string path = string_utils::utf8ToSystem(globalEnvPath.absolutePath());
   Error error = r::exec::executeSafely(
                    boost::bind(R_SaveGlobalEnvToFile, path.c_str()));
   
   if (error)
   {
      return error;
   }
   else
   {
      return Success();
   }
}



} // anonymous namespace

bool imageIsDirty()
{
   return R_DirtyImage != 0;
}

void setImageDirty(bool imageDirty)
{
   R_DirtyImage = imageDirty ? 1 : 0;
}

void setRCallbacks(const RCallbacks& callbacks)
{
   s_callbacks = callbacks;
}

RCallbacks& rCallbacks()
{
   return s_callbacks;
}

InternalCallbacks* stdInternalCallbacks()
{
   return &s_internalCallbacks;
}

int RReadConsole (const char *pmt,
                  CONSOLE_BUFFER_CHAR* buf,
                  int buflen,
                  int hist)
{
   try
   {
      // capture the prompt for later manipulation
      std::string prompt(pmt);

      // invoke one time initialization
      if (!s_initialized)
      {
         // ignore interrupts which occur during initialization. any
         // interrupt will cause the initialization to fail which will
         // then require the user to start over from the beginning. if the
         // user has to wait in any case we might as well help them out by
         // never having to start from scratch
         r::exec::IgnoreInterruptsScope ignoreInterrupts;

         // attempt to initialize 
         Error initError;
         Error error = r::exec::executeSafely<Error>(initialize, &initError);
         if (error || initError)
         {
            if (initError)
               error = initError;

            // log the error if it was unexpected
            if (!error.expected())
               LOG_ERROR(error);
            
            // terminate the session (use suicide so that no special
            // termination code runs -- i.e. call to setAbnormalEnd(false)
            // or call to client::quitSession)
            rSuicideError(error);
         }
         
         // reset the prompt to whatever the default is after we've
         // fully initialized (and restored suspended options)
         prompt = r::options::getOption<std::string>("prompt");

         // ensure only one initialization 
         s_initialized = true;
      }

      std::string promptString(prompt);
      promptString = util::rconsole2utf8(promptString);

      // get the next input
      bool addToHistory = (hist == 1);
      RConsoleInput consoleInput("");
      if (s_callbacks.consoleRead(promptString, addToHistory, &consoleInput) )
      {
         // add prompt to console actions (we do this after consoleRead
         // completes so that we don't send both a console prompt event
         // AND include the same prompt in the actions history)
         consoleActions().add(kConsoleActionPrompt, prompt);

         if (consoleInput.cancel)
         {
            // notify of interrupt
            consoleActions().notifyInterrupt();

            // escape out using exception so that we can allow normal
            // c++ stack unwinding to occur before jumping
            throw r::exec::InterruptException();
         }
         else
         {
            // determine the input to return to R
            std::string rInput = consoleInput.text;

            // refresh source if necessary (no-op in production)
            r::sourceManager().reloadIfNecessary();
            
            // ensure that our input fits within the buffer
            std::string::size_type maxLen = buflen - 2; // for \n\0
            rInput = string_utils::utf8ToSystem(rInput, true);
            if (rInput.length() > maxLen)
               rInput.resize(maxLen);
            std::string::size_type inputLen = rInput.length();
            
            // add to console actions and history (if requested). note that
            // we add the user's input rather than any tranformed input we
            // created as a result of a shell escape
            consoleActions().add(kConsoleActionInput, consoleInput.text);
            if (addToHistory && !isInjectedBrowserCommand(consoleInput.text))
               consoleHistory().add(consoleInput.text);

            // call console input hook and interrupt if the hook tells us to
            if (!consoleInputHook(prompt, consoleInput.text))
               throw r::exec::InterruptException();

            // copy to buffer and add terminators
            rInput.copy( (char*)buf, maxLen);
            buf[inputLen] = '\n';
            buf[inputLen+1] = '\0';
         }

         return 1 ;
      }
      else
      {
         // buffer not ready
         return 0;
      }
   }
   catch(r::exec::InterruptException&)
   {
      // set interrupts pending
      r::exec::setInterruptsPending(true);

      // only issue an interrupt when not on Windows -- let the regular
      // event loop handle interrupts there. note that this will longjmp
#ifndef _WIN32
      r::exec::checkUserInterrupt();
#endif

      // buffer not ready
      return 0;
   }
   catch(const std::exception& e)
   {
      std::string msg = std::string("Unexpected exception: ") + e.what();
      LOG_ERROR_MESSAGE(msg);
      rSuicide(msg);
   }
   catch(...)
   {
      std::string msg = "Unknown exception";
      LOG_ERROR_MESSAGE(msg);
      rSuicide(msg);
   }
      
   return 0; // keep compiler happy
}
   
void RShowMessage(const char* msg)
{
   try 
   {
      s_callbacks.showMessage(msg) ;<commit_msg>call Rf_onintr directly when handling interrupts<commit_after>/*
 * RStdCallbacks.cpp
 *
 * Copyright (C) 2009-18 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#define R_INTERNAL_FUNCTIONS

#include <boost/function.hpp>
#include <boost/regex.hpp>

#include <r/RExec.hpp>
#include <r/ROptions.hpp>
#include <r/RSourceManager.hpp>
#include <r/RUtil.hpp>
#include <r/session/RClientState.hpp>
#include <r/session/RConsoleActions.hpp>
#include <r/session/RConsoleHistory.hpp>
#include <r/session/RSession.hpp>
#include <r/session/RSessionState.hpp>

#include <core/FileSerializer.hpp>
#include <core/RegexUtils.hpp>

#include "RInit.hpp"
#include "REmbedded.hpp"
#include "RStdCallbacks.hpp"
#include "RQuit.hpp"
#include "RSuspend.hpp"

#include "graphics/RGraphicsDevDesc.hpp"
#include "graphics/RGraphicsUtils.hpp"
#include "graphics/RGraphicsDevice.hpp"
#include "graphics/RGraphicsPlotManager.hpp"

#include <Rembedded.h>

extern "C" {

#ifndef _WIN32
SA_TYPE SaveAction;
#else
__declspec(dllimport) SA_TYPE SaveAction;
#endif

}

using namespace rstudio::core;

namespace rstudio {
namespace r {
namespace session {

namespace {

// have we completed our one-time initialization yet?
bool s_initialized = false;

// main callbacks; pointer to statically allocated memory
RCallbacks s_callbacks;

// internal callbacks for delegation
InternalCallbacks s_internalCallbacks;

// temporarily suppress output
bool s_suppressOutput = false;

class JumpToTopException
{
};

FilePath rSaveGlobalEnvironmentFilePath()
{
   FilePath rEnvironmentDir = utils::rEnvironmentDir();
   return rEnvironmentDir.complete(".RData");
}

void rSuicideError(const Error& error)
{
   // provide error message if the error was unexpected
   std::string msg;
   if (!error.expected())
      msg = core::log::errorAsLogEntry(error);

   rSuicide(msg);
}

SA_TYPE saveAsk()
{
   try
   {
      // end user prompt
      std::string wsPath = FilePath::createAliasedPath(
            rSaveGlobalEnvironmentFilePath(), utils::userHomePath());
      std::string prompt = "Save workspace image to " + wsPath + "? [y/n";
      // The Rf_jump_to_top_level doesn't work (freezes the process) with
      // 64-bit mingw due to the way it does stack unwinding. Since this is
      // a farily obscure gesture (quit from command line then cancel the quit)
      // we just eliminate the possiblity of it on windows
#ifndef _WIN32
      prompt += "/c";
#endif
      prompt += "]: ";

      // input buffer
      std::vector<CONSOLE_BUFFER_CHAR> inputBuffer(512, 0);

      while(true)
      {
         // read input
         RReadConsole(prompt.c_str(), &(inputBuffer[0]), inputBuffer.size(), 0);
         std::string input(1, inputBuffer[0]);
         boost::algorithm::to_lower(input);

         // look for yes, no, or cancel
         if (input == "y")
            return SA_SAVE;
         else if (input == "n")
            return SA_NOSAVE;
#ifndef _WIN32
         else if (input == "c")
            throw JumpToTopException();
#endif
      }
   }
   catch(JumpToTopException)
   {
      Rf_jump_to_toplevel();
   }

   // keep compiler happy
   return SA_SAVE;
}

void doHistoryFileOperation(SEXP args, 
                            boost::function<Error(const FilePath&)> fileOp)
{
   // validate filename argument
   SEXP file = CAR(args);
   if (!sexp::isString(file) || sexp::length(file) < 1)
      throw r::exec::RErrorException("invalid 'file' argument");
   
   // calculate full path to history file
   FilePath historyFilePath(R_ExpandFileName(Rf_translateChar(STRING_ELT(file, 
                                                                         0))));
   // perform operation
   Error error = fileOp(historyFilePath);
   if (error)
      throw r::exec::RErrorException(error.code().message());
}
   
bool consoleInputHook(const std::string& prompt,
                      const std::string& input)
{
   // only check for quit when we're at the default prompt
   if (!r::session::utils::isDefaultPrompt(prompt))
      return true;

   // check for user quit invocation
    boost::regex re("^\\s*(q|quit)\\s*\\(.*$");
    boost::smatch match;
    if (regex_utils::match(input, match, re))
   {
      if (!s_callbacks.handleUnsavedChanges())
      {
         REprintf("User cancelled quit operation\n");
         return false;
      }

      // on win32 we will actually assume responsibility for the
      // quit function entirely (so we can call our internal cleanup
      // handler code)
#ifdef _WIN32
      std::string quitErr;
      bool didQuit = win32Quit(input, &quitErr);
      if (!didQuit)
         REprintf((quitErr + "\n").c_str());

      // always return false (since we take over the command fully)
      return false;
#else
      return true;
#endif
   }
   else
   {
      return true;
   }
}

bool isInjectedBrowserCommand(const std::string& cmd)
{
   return browserContextActive() &&
          (cmd == "c" || cmd == "Q" || cmd == "n" || cmd == "s" || cmd == "f");
}

Error saveDefaultGlobalEnvironment()
{
   // path to save to
   FilePath globalEnvPath = rSaveGlobalEnvironmentFilePath();

   // notify client of serialization status
   SerializationCallbackScope cb(kSerializationActionSaveDefaultWorkspace,
                                 globalEnvPath);

   // suppress interrupts which occur during saving
   r::exec::IgnoreInterruptsScope ignoreInterrupts;
         
   // save global environment
   std::string path = string_utils::utf8ToSystem(globalEnvPath.absolutePath());
   Error error = r::exec::executeSafely(
                    boost::bind(R_SaveGlobalEnvToFile, path.c_str()));
   
   if (error)
   {
      return error;
   }
   else
   {
      return Success();
   }
}



} // anonymous namespace

bool imageIsDirty()
{
   return R_DirtyImage != 0;
}

void setImageDirty(bool imageDirty)
{
   R_DirtyImage = imageDirty ? 1 : 0;
}

void setRCallbacks(const RCallbacks& callbacks)
{
   s_callbacks = callbacks;
}

RCallbacks& rCallbacks()
{
   return s_callbacks;
}

InternalCallbacks* stdInternalCallbacks()
{
   return &s_internalCallbacks;
}

int RReadConsole (const char *pmt,
                  CONSOLE_BUFFER_CHAR* buf,
                  int buflen,
                  int hist)
{
   try
   {
      // capture the prompt for later manipulation
      std::string prompt(pmt);

      // invoke one time initialization
      if (!s_initialized)
      {
         // ignore interrupts which occur during initialization. any
         // interrupt will cause the initialization to fail which will
         // then require the user to start over from the beginning. if the
         // user has to wait in any case we might as well help them out by
         // never having to start from scratch
         r::exec::IgnoreInterruptsScope ignoreInterrupts;

         // attempt to initialize 
         Error initError;
         Error error = r::exec::executeSafely<Error>(initialize, &initError);
         if (error || initError)
         {
            if (initError)
               error = initError;

            // log the error if it was unexpected
            if (!error.expected())
               LOG_ERROR(error);
            
            // terminate the session (use suicide so that no special
            // termination code runs -- i.e. call to setAbnormalEnd(false)
            // or call to client::quitSession)
            rSuicideError(error);
         }
         
         // reset the prompt to whatever the default is after we've
         // fully initialized (and restored suspended options)
         prompt = r::options::getOption<std::string>("prompt");

         // ensure only one initialization 
         s_initialized = true;
      }

      std::string promptString(prompt);
      promptString = util::rconsole2utf8(promptString);

      // get the next input
      bool addToHistory = (hist == 1);
      RConsoleInput consoleInput("");
      if (s_callbacks.consoleRead(promptString, addToHistory, &consoleInput) )
      {
         // add prompt to console actions (we do this after consoleRead
         // completes so that we don't send both a console prompt event
         // AND include the same prompt in the actions history)
         consoleActions().add(kConsoleActionPrompt, prompt);

         if (consoleInput.cancel)
         {
            // notify of interrupt
            consoleActions().notifyInterrupt();

            // escape out using exception so that we can allow normal
            // c++ stack unwinding to occur before jumping
            throw r::exec::InterruptException();
         }
         else
         {
            // determine the input to return to R
            std::string rInput = consoleInput.text;

            // refresh source if necessary (no-op in production)
            r::sourceManager().reloadIfNecessary();
            
            // ensure that our input fits within the buffer
            std::string::size_type maxLen = buflen - 2; // for \n\0
            rInput = string_utils::utf8ToSystem(rInput, true);
            if (rInput.length() > maxLen)
               rInput.resize(maxLen);
            std::string::size_type inputLen = rInput.length();
            
            // add to console actions and history (if requested). note that
            // we add the user's input rather than any tranformed input we
            // created as a result of a shell escape
            consoleActions().add(kConsoleActionInput, consoleInput.text);
            if (addToHistory && !isInjectedBrowserCommand(consoleInput.text))
               consoleHistory().add(consoleInput.text);

            // call console input hook and interrupt if the hook tells us to
            if (!consoleInputHook(prompt, consoleInput.text))
               throw r::exec::InterruptException();

            // copy to buffer and add terminators
            rInput.copy( (char*)buf, maxLen);
            buf[inputLen] = '\n';
            buf[inputLen+1] = '\0';
         }

         return 1 ;
      }
      else
      {
         // buffer not ready
         return 0;
      }
   }
   catch(r::exec::InterruptException&)
   {
      // set interrupts pending
      r::exec::setInterruptsPending(true);

      // call R interrupt handler. note that previously we used
      // r::exec::checkUserInterrupt(), but this also has the side effect of
      // calling R_ProcessEvents(). this means that modules with registered
      // event handlers may get a chance to handle our interrupt, and so the
      // interrupt will be handled in an incorrect context. this can lead to
      // issues like the process exiting prematurely. see:
      //
      //     https://github.com/rstudio/rstudio/issues/5108
      //
      // for one such example.
      //
      // note that on Windows we let the regular R_ProcessEvents() machinery
      // handle the interrupt as we noticed calling Rf_onintr() in that
      // environment could cause a crash
#ifndef _WIN32
      Rf_onintr();
#endif

      // buffer not ready
      return 0;
   }
   catch(const std::exception& e)
   {
      std::string msg = std::string("Unexpected exception: ") + e.what();
      LOG_ERROR_MESSAGE(msg);
      rSuicide(msg);
   }
   catch(...)
   {
      std::string msg = "Unknown exception";
      LOG_ERROR_MESSAGE(msg);
      rSuicide(msg);
   }
      
   return 0; // keep compiler happy
}
   
void RShowMessage(const char* msg)
{
   try 
   {
      s_callbacks.showMessage(msg) ;
{'fraction_non_alphanumeric': 0.10284605433376455, 'fraction_numerical': 0.004570935748167314, 'mean_word_length': 2.4250479988184908, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 1, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7302375', 'n_tokens_mistral': 6671, 'n_tokens_neox': 6443, 'n_words': 1958}