1 /*
2  * SessionAsyncCompletions.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 // #define RSTUDIO_DEBUG_LABEL "pkginfo"
17 // #define RSTUDIO_ENABLE_DEBUG_MACROS
18 
19 #include "SessionAsyncPackageInformation.hpp"
20 
21 #include <string>
22 #include <vector>
23 #include <sstream>
24 
25 #include <shared_core/FilePath.hpp>
26 #include <shared_core/json/Json.hpp>
27 #include <core/json/JsonRpc.hpp>
28 #include <shared_core/Error.hpp>
29 
30 #include <boost/format.hpp>
31 #include <boost/algorithm/string.hpp>
32 
33 #include <session/SessionModuleContext.hpp>
34 
35 #include <core/Macros.hpp>
36 
37 namespace rstudio {
38 namespace session {
39 namespace modules {
40 namespace r_packages {
41 
42 using namespace core::r_util;
43 
44 // static variables
45 bool AsyncPackageInformationProcess::s_isUpdating_ = false;
46 bool AsyncPackageInformationProcess::s_updateRequested_ = false;
47 std::vector<std::string> AsyncPackageInformationProcess::s_pkgsToUpdate_;
48 
49 using namespace rstudio::core;
50 
51 class CompleteUpdateOnExit : public boost::noncopyable {
52 
53 public:
54 
~CompleteUpdateOnExit()55    ~CompleteUpdateOnExit()
56    {
57       using namespace rstudio::core::r_util;
58 
59       // Give empty completions to the packages which weren't updated
60       for (std::vector<std::string>::const_iterator it = AsyncPackageInformationProcess::s_pkgsToUpdate_.begin();
61            it != AsyncPackageInformationProcess::s_pkgsToUpdate_.end();
62            ++it)
63       {
64          if (!RSourceIndex::hasInformation(*it))
65          {
66             RSourceIndex::addPackageInformation(*it, PackageInformation());
67          }
68       }
69 
70       AsyncPackageInformationProcess::s_pkgsToUpdate_.clear();
71       AsyncPackageInformationProcess::s_isUpdating_ = false;
72 
73       if (AsyncPackageInformationProcess::s_updateRequested_)
74       {
75          AsyncPackageInformationProcess::s_updateRequested_ = false;
76          AsyncPackageInformationProcess::update();
77       }
78    }
79 
80 };
81 
82 namespace {
83 
fillFormalInfo(const json::Array & formalNamesJson,const json::Array & formalInfoJsonArray,FunctionInformation * pInfo)84 void fillFormalInfo(const json::Array& formalNamesJson,
85                     const json::Array& formalInfoJsonArray,
86                     FunctionInformation* pInfo)
87 {
88    for (std::size_t i = 0, n = formalNamesJson.getSize(); i < n; ++i)
89    {
90       std::string formalName = formalNamesJson[i].getString();
91       FormalInformation info(formalName);
92 
93       const json::Array& formalInfoJson = formalInfoJsonArray[i].getArray();
94 
95       bool hasDefaultValue = formalInfoJson[0].getInt() != 0;
96       bool isMissingnessHandled = formalInfoJson[1].getInt() != 0;
97       bool isUsed = formalInfoJson[2].getInt() != 0;
98 
99       info.setHasDefaultValue(hasDefaultValue);
100       info.setMissingnessHandled(isMissingnessHandled);
101       info.setIsUsed(isUsed);
102 
103       pInfo->addFormal(info);
104    }
105 }
106 
fillFunctionInfo(const json::Object & functionObjectJson,const std::string & pkgName,std::map<std::string,FunctionInformation> * pInfo)107 bool fillFunctionInfo(const json::Object& functionObjectJson,
108                       const std::string& pkgName,
109                       std::map<std::string, FunctionInformation>* pInfo)
110 {
111    using namespace core::json;
112 
113    for (const json::Object::Member& member : functionObjectJson)
114    {
115       const std::string& functionName = member.getName();
116       FunctionInformation info(functionName, pkgName);
117 
118       const json::Value& valueJson = member.getValue();
119 
120       json::Array formalNamesJson;
121       json::Array formalInfoJson;
122       int performsNse = 0;
123       Error error = json::readObject(valueJson.getObject(),
124                                      "formal_names", formalNamesJson,
125                                      "formal_info",  formalInfoJson,
126                                      "performs_nse", performsNse);
127 
128       if (error)
129          LOG_ERROR(error);
130 
131       info.setPerformsNse(performsNse);
132       info.setIsPrimitive(false);
133 
134       fillFormalInfo(formalNamesJson, formalInfoJson, &info);
135 
136       (*pInfo)[functionName] = info;
137    }
138 
139    return true;
140 
141 }
142 
143 } // anonymous namespace
144 
onCompleted(int exitStatus)145 void AsyncPackageInformationProcess::onCompleted(int exitStatus)
146 {
147    CompleteUpdateOnExit updateScope;
148 
149    DEBUG("* Completed async library lookup");
150    std::vector<std::string> splat;
151 
152    std::string stdOut = stdOut_.str();
153 
154    stdOut_.str(std::string());
155    stdOut_.clear();
156 
157    if (stdOut == "" || stdOut == "\n")
158    {
159       DEBUG("- Received empty response");
160       return;
161    }
162 
163    boost::split(splat, stdOut, boost::is_any_of("\n"));
164 
165    std::size_t n = splat.size();
166    DEBUG("- Received " << n << " lines of response");
167 
168    // Each line should be a JSON object with the format:
169    //
170    // {
171    //    "package": <single package name>
172    //    "exports": <array of object names in the namespace>,
173    //    "types": <array of types (see .rs.acCompletionTypes)>,
174    //    "function_info": {big ugly object with function info},
175    //    "data" <array of dataset names>
176    // }
177    for (std::size_t i = 0; i < n; ++i)
178    {
179       json::Array exportsJson;
180       json::Array typesJson;
181       json::Object functionInfoJson;
182       json::Array datasetsJson;
183 
184       core::r_util::PackageInformation pkgInfo;
185 
186       if (splat[i].empty())
187          continue;
188 
189       // The lines we wish to parse should be prefixed with
190       // the code '#!json: '.
191       if (!boost::algorithm::starts_with(splat[i], "#!json: "))
192          continue;
193 
194       std::string line = splat[i].substr(::strlen("#!json: "));
195 
196       json::Value value;
197       if (!value.parse(line))
198       {
199          std::string subset;
200          if (splat[i].length() > 60)
201             subset = splat[i].substr(0, 60) + "...";
202          else
203             subset = splat[i];
204 
205          LOG_ERROR_MESSAGE("Failed to parse JSON: '" + subset + "'");
206          continue;
207       }
208 
209       // Ensure that this parsed as an Object -- this might have parsed as
210       // something else if e.g. we got malformed output on load of a package
211       if (!json::isType<json::Object>(value))
212          continue;
213 
214       Error error = json::readObject(value.getObject(),
215                                      "package", pkgInfo.package,
216                                      "exports", exportsJson,
217                                      "types", typesJson,
218                                      "function_info", functionInfoJson,
219                                      "datasets", datasetsJson);
220 
221       if (error)
222       {
223          LOG_ERROR(error);
224          continue;
225       }
226 
227       DEBUG("Adding entry for package: '" << pkgInfo.package << "'");
228 
229       if (!exportsJson.toVectorString(pkgInfo.exports))
230          LOG_ERROR_MESSAGE("Failed to read JSON 'objects' array to vector");
231 
232       if (!typesJson.toVectorInt(pkgInfo.types))
233          LOG_ERROR_MESSAGE("Failed to read JSON 'types' array to vector");
234 
235       if (!fillFunctionInfo(functionInfoJson, pkgInfo.package, &(pkgInfo.functionInfo)))
236          LOG_ERROR_MESSAGE("Failed to read JSON 'functions' object to map");
237 
238       if (!datasetsJson.toVectorString(pkgInfo.datasets))
239          LOG_ERROR_MESSAGE("Failed to read JSON 'data' array to vector");
240 
241       // Update the index
242       core::r_util::RSourceIndex::addPackageInformation(pkgInfo.package, pkgInfo);
243    }
244 
245 }
246 
update()247 void AsyncPackageInformationProcess::update()
248 {
249    using namespace rstudio::core::r_util;
250 
251    s_updateRequested_ = true;
252    if (s_isUpdating_)
253       return;
254 
255    s_isUpdating_ = true;
256    s_updateRequested_ = false;
257 
258    s_pkgsToUpdate_ =
259       RSourceIndex::getAllUnindexedPackages();
260 
261    // alias for readability
262    const std::vector<std::string>& pkgs = s_pkgsToUpdate_;
263 
264    DEBUG_BLOCK("Completions")
265    {
266       if (!pkgs.empty())
267       {
268          std::cerr << "Updating packages: [";
269          std::cerr << "'" << pkgs[0] << "'";
270          for (std::size_t i = 1; i < pkgs.size(); i++)
271          {
272             std::cerr << ", '" << pkgs[i] << "'";
273          }
274          std::cerr << "]\n";
275       }
276       else
277       {
278          std::cerr << "No packages to update; bailing out" << std::endl;
279       }
280    }
281 
282    if (pkgs.empty())
283    {
284       s_isUpdating_ = false;
285       return;
286    }
287 
288    std::stringstream ss;
289    ss << ".rs.getPackageInformation(";
290    ss << "'" << pkgs[0] << "'";
291 
292    if (pkgs.size() > 0)
293    {
294       for (std::vector<std::string>::const_iterator it = pkgs.begin() + 1;
295            it != pkgs.end();
296            ++it)
297       {
298          ss << ",'" << *it << "'";
299       }
300    }
301    ss << ");";
302 
303    std::string finalCmd = ss.str();
304    DEBUG("Running command: '" << finalCmd << "'");
305 
306    boost::shared_ptr<AsyncPackageInformationProcess> pProcess(
307          new AsyncPackageInformationProcess());
308 
309    std::vector<core::FilePath> sources;
310    FilePath modulesPath = session::options().modulesRSourcePath();
311    sources.push_back(modulesPath.completePath("SessionCodeTools.R"));
312    sources.push_back(modulesPath.completePath("SessionRCompletions.R"));
313 
314    pProcess->start(
315             finalCmd.c_str(),
316             core::FilePath(),
317             async_r::R_PROCESS_VANILLA | async_r::R_PROCESS_AUGMENTED,
318             sources);
319 
320 }
321 
322 } // end namespace r_pacakges
323 } // end namespace modules
324 } // end namespace session
325 } // end namespace rstudio
326