1 /*
2  * SessionErrors.cpp
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 
16 #include <algorithm>
17 
18 #include <shared_core/Error.hpp>
19 #include <core/Exec.hpp>
20 #include <core/json/JsonRpc.hpp>
21 
22 #include <r/RExec.hpp>
23 #include <r/ROptions.hpp>
24 
25 #include <boost/bind/bind.hpp>
26 #include <session/SessionModuleContext.hpp>
27 #include <session/prefs/UserPrefs.hpp>
28 #include <session/prefs/UserState.hpp>
29 #include "SessionErrors.hpp"
30 #include "SessionBreakpoints.hpp"
31 
32 using namespace rstudio::core;
33 using namespace boost::placeholders;
34 
35 namespace rstudio {
36 namespace session {
37 namespace modules {
38 namespace errors {
39 namespace {
40 
enqueErrorHandlerChanged(const std::string & type)41 void enqueErrorHandlerChanged(const std::string& type)
42 {
43    json::Object errorHandlerType;
44    errorHandlerType["type"] = type;
45    ClientEvent errorHandlerChanged(
46             client_events::kErrorHandlerChanged, errorHandlerType);
47    module_context::enqueClientEvent(errorHandlerChanged);
48 }
49 
setErrHandler(const std::string & type,bool inMyCode,boost::shared_ptr<SEXP> pErrorHandler)50 Error setErrHandler(const std::string& type, bool inMyCode,
51                     boost::shared_ptr<SEXP> pErrorHandler)
52 {
53    // when setting the error handler to "custom", just leave it as it was
54    if (type == kErrorHandlerTypeCustom)
55       return Success();
56 
57    Error error = r::exec::RFunction(
58             ".rs.setErrorManagementType", type, inMyCode)
59             .callUnsafe();
60    if (error)
61       return error;
62 
63    *pErrorHandler = r::options::getOption("error");
64    return Success();
65 }
66 
setErrHandlerType(const std::string & type,boost::shared_ptr<SEXP> pErrorHandler)67 Error setErrHandlerType(const std::string& type,
68                         boost::shared_ptr<SEXP> pErrorHandler)
69 {
70    Error error = setErrHandler(type,
71                                prefs::userPrefs().handleErrorsInUserCodeOnly(),
72                                pErrorHandler);
73    if (error)
74       return error;
75 
76    prefs::userState().setErrorHandlerType(type);
77    enqueErrorHandlerChanged(type);
78    return Success();
79 }
80 
setErrHandlerType(boost::shared_ptr<SEXP> pErrorHandler,const json::JsonRpcRequest & request,json::JsonRpcResponse * pResponse)81 Error setErrHandlerType(boost::shared_ptr<SEXP> pErrorHandler,
82                         const json::JsonRpcRequest& request,
83                         json::JsonRpcResponse* pResponse)
84 {
85    std::string type;
86    Error error = json::readParams(request.params, &type);
87    if (error)
88       return error;
89 
90    return setErrHandlerType(type, pErrorHandler);
91 }
92 
initializeErrManagement(boost::shared_ptr<SEXP> pErrorHandler,boost::shared_ptr<bool> pHandleUserErrorsOnly)93 Error initializeErrManagement(boost::shared_ptr<SEXP> pErrorHandler,
94                               boost::shared_ptr<bool> pHandleUserErrorsOnly)
95 {
96    SEXP currentHandler = r::options::getOption("error");
97    *pHandleUserErrorsOnly = prefs::userPrefs().handleErrorsInUserCodeOnly();
98    // This runs after ~/.RProfile, so don't change the error handler if
99    // there's already one assigned, or if we're aware of a custom error
100    // handler.
101    if (currentHandler == R_NilValue &&
102        prefs::userState().errorHandlerType() != kErrorHandlerTypeCustom)
103       setErrHandlerType(prefs::userState().errorHandlerType(), pErrorHandler);
104    else
105       prefs::userState().setErrorHandlerType(kErrorHandlerTypeCustom);
106    return Success();
107 }
108 
109 // Detect whether the error handler has changed, and optionally record the
110 // change as a permanent. (We don't make changes permanent when they're
111 // detected during a session.)
detectHandlerChange(boost::shared_ptr<SEXP> pErrorHandler,bool recordSetting)112 void detectHandlerChange(boost::shared_ptr<SEXP> pErrorHandler,
113                          bool recordSetting)
114 {
115    // check to see if the error option has been changed from beneath us; if
116    // it has, emit a client event so the client doesn't show an incorrect
117    // error handler
118    SEXP currentHandler = r::options::getOption("error");
119    if (currentHandler != *pErrorHandler)
120    {
121       std::string handlerType;
122       if (currentHandler != R_NilValue &&
123           r::sexp::isLanguage(currentHandler))
124       {
125          // it's possible for the SEXP to change (it's a pointer) even though
126          // the handler is correct; check the attribute of the function invoked
127          // by the handler and compare to the user preference.
128          SEXP fun = CAR(currentHandler);
129          SEXP typeSEXP = r::sexp::getAttrib(fun, "errorHandlerType");
130          if (typeSEXP != nullptr && !r::sexp::isNull(typeSEXP))
131          {
132             Error error = r::sexp::extract(typeSEXP, &handlerType);
133             if (!error && handlerType == prefs::userState().errorHandlerType())
134             {
135                // the SEXP is different but the attribute matches; update our
136                // SEXP so we don't keep detecting a change
137                *pErrorHandler = currentHandler;
138                return;
139             }
140          }
141       }
142       *pErrorHandler = currentHandler;
143 
144       // attempt to figure out what error handler type is in use, if any
145       if (handlerType.empty())
146       {
147          handlerType = (currentHandler == R_NilValue) ?
148                         kErrorHandlerTypeMessage :
149                         kErrorHandlerTypeCustom;
150       }
151       if (recordSetting)
152          prefs::userState().setErrorHandlerType(handlerType);
153 
154       // the notebook error handler is transient, so don't send it to the
155       // client
156       if (handlerType != kErrorHandlerTypeNotebook)
157          enqueErrorHandlerChanged(handlerType);
158    }
159 }
160 
onUserSettingsChanged(const std::string & pref,boost::shared_ptr<SEXP> pErrorHandler,boost::shared_ptr<bool> pHandleUserErrorsOnly)161 void onUserSettingsChanged(const std::string& pref,
162                            boost::shared_ptr<SEXP> pErrorHandler,
163                            boost::shared_ptr<bool> pHandleUserErrorsOnly)
164 {
165    // check to see if the setting for 'handle errors in user code only'
166    // has been changed since we last looked at it; if it has, switch the
167    // error handler in a corresponding way
168    if (pref != kErrorHandlerType)
169       return;
170 
171    bool handleUserErrorsOnly = prefs::userPrefs().handleErrorsInUserCodeOnly();
172    Error error = setErrHandler(prefs::userState().errorHandlerType(),
173                                handleUserErrorsOnly,
174                                pErrorHandler);
175    if (error)
176       LOG_ERROR(error);
177 
178    *pHandleUserErrorsOnly = handleUserErrorsOnly;
179 }
180 
181 } // anonymous namespace
182 
errorStateAsJson()183 json::Value errorStateAsJson()
184 {
185    json::Object state;
186    state["error_handler_type"] = prefs::userState().errorHandlerType();
187    return std::move(state);
188 }
189 
initialize()190 Error initialize()
191 {
192    boost::shared_ptr<SEXP> pErrorHandler =
193          boost::make_shared<SEXP>(R_NilValue);
194    boost::shared_ptr<bool> pHandleUserErrorsOnly =
195          boost::make_shared<bool>(true);
196 
197    using boost::bind;
198    using namespace module_context;
199 
200    // Check to see whether the error handler has changed immediately after init
201    // (to find changes from e.g. .Rprofile) and after every console prompt.
202    events().onConsolePrompt.connect(bind(detectHandlerChange,
203                                          pErrorHandler, false));
204    events().onDeferredInit.connect(bind(detectHandlerChange,
205                                         pErrorHandler, true));
206    prefs::userState().onChanged.connect(bind(onUserSettingsChanged,
207                                          _2,
208                                          pErrorHandler,
209                                          pHandleUserErrorsOnly));
210 
211    json::JsonRpcFunction setErrMgmt =
212          bind(setErrHandlerType, pErrorHandler, _1, _2);
213 
214    ExecBlock initBlock;
215    initBlock.addFunctions()
216       (bind(registerRpcMethod, "set_error_management_type", setErrMgmt))
217       (bind(sourceModuleRFile, "SessionErrors.R"))
218       (bind(initializeErrManagement, pErrorHandler, pHandleUserErrorsOnly));
219 
220    return initBlock.execute();
221 }
222 
223 } // namespace errors
224 } // namespace modules
225 } // namespace session
226 } // namespace rstudio
227 
228 
229