PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/src/cpp/session/modules/clang/FindReferences.cpp

http://github.com/rstudio/rstudio
C++ | 453 lines | 316 code | 71 blank | 66 comment | 57 complexity | 64ad1fca8f373ad01703d9d8201c1c61 MD5 | raw file
Possible License(s): AGPL-3.0, MIT, MPL-2.0-no-copyleft-exception, BSD-3-Clause, Apache-2.0
  1. /*
  2. * FindReferences.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. #include "FindReferences.hpp"
  16. #include <gsl/gsl>
  17. #include <boost/algorithm/string/predicate.hpp>
  18. #include <boost/algorithm/string/split.hpp>
  19. #include <core/FileSerializer.hpp>
  20. #include <core/libclang/LibClang.hpp>
  21. #include <core/system/ProcessArgs.hpp>
  22. #include <session/SessionModuleContext.hpp>
  23. #include "RSourceIndex.hpp"
  24. #include "RCompilationDatabase.hpp"
  25. using namespace rstudio::core;
  26. using namespace rstudio::core::libclang;
  27. namespace rstudio {
  28. namespace session {
  29. namespace modules {
  30. namespace clang {
  31. namespace {
  32. struct FindReferencesData
  33. {
  34. FindReferencesData(CXTranslationUnit tu, const std::string& USR)
  35. : tu(tu), USR(USR)
  36. {
  37. }
  38. CXTranslationUnit tu;
  39. std::string USR;
  40. std::string spelling;
  41. std::vector<FileRange> references;
  42. };
  43. // if the USR's differ only by an ending # consider them equal
  44. // we do this because references to function declarations seem to
  45. // accue an extra # within their USRs
  46. bool equalUSR(const std::string& USR1, const std::string& USR2)
  47. {
  48. using namespace boost::algorithm;
  49. bool endHashUSR1 = ends_with(USR1, "#");
  50. bool endHashUSR2 = ends_with(USR2, "#");
  51. if ((endHashUSR1 && endHashUSR2) || (!endHashUSR1 && !endHashUSR2))
  52. {
  53. return USR1 == USR2;
  54. }
  55. else if (endHashUSR1)
  56. {
  57. return USR1.substr(0, USR1.length() - 1) == USR2;
  58. }
  59. else
  60. {
  61. return USR2.substr(0, USR2.length() - 1) == USR1;
  62. }
  63. }
  64. CXChildVisitResult findReferencesVisitor(CXCursor cxCursor,
  65. CXCursor,
  66. CXClientData data)
  67. {
  68. using namespace rstudio::core::libclang;
  69. // get pointer to data struct
  70. FindReferencesData* pData = (FindReferencesData*)data;
  71. // reference to the cursor (ensure valid)
  72. Cursor cursor(cxCursor);
  73. if (!cursor.isValid())
  74. return CXChildVisit_Continue;
  75. // continue with sibling if it's not from the main file
  76. SourceLocation location = cursor.getSourceLocation();
  77. if (!location.isFromMainFile())
  78. return CXChildVisit_Continue;
  79. // get referenced cursor
  80. Cursor referencedCursor = cursor.getReferenced();
  81. if (referencedCursor.isValid() && referencedCursor.isDeclaration())
  82. {
  83. // check for matching USR
  84. if (equalUSR(referencedCursor.getUSR(), pData->USR))
  85. {
  86. // tokenize to extract identifer location for cursors that
  87. // represent larger source constructs
  88. FileRange foundRange;
  89. libclang::Tokens tokens(pData->tu, cursor.getExtent());
  90. std::vector<unsigned> indexes;
  91. // for constructors & destructors we search backwards so that the
  92. // match is for the constructor identifier rather than the class
  93. // identifer
  94. unsigned numTokens = tokens.numTokens();
  95. if (referencedCursor.getKind() == CXCursor_Constructor ||
  96. referencedCursor.getKind() == CXCursor_Destructor)
  97. {
  98. for (unsigned i = 0; i < numTokens; i++)
  99. indexes.push_back(numTokens - i - 1);
  100. }
  101. else
  102. {
  103. for (unsigned i = 0; i < numTokens; i++)
  104. indexes.push_back(i);
  105. }
  106. // cycle through the tokens
  107. for (unsigned i : indexes)
  108. {
  109. Token token = tokens.getToken(i);
  110. if (token.kind() == CXToken_Identifier &&
  111. token.spelling() == cursor.spelling())
  112. {
  113. // record spelling if necessary
  114. if (pData->spelling.empty())
  115. pData->spelling = cursor.spelling();
  116. // record the range
  117. foundRange = token.extent().getFileRange();
  118. break;
  119. }
  120. }
  121. // if we didn't find an identifier that matches use the
  122. // original match (i.e. important for constructors where
  123. // the 'spelling' of the invocation is the name of the
  124. // variable declared)
  125. if (foundRange.empty())
  126. foundRange = cursor.getExtent().getFileRange();
  127. // record the range if it's not a duplicate of the previous range
  128. if (pData->references.empty() ||
  129. (pData->references.back() != foundRange))
  130. {
  131. pData->references.push_back(foundRange);
  132. }
  133. }
  134. }
  135. // recurse into namespaces, classes, etc.
  136. return CXChildVisit_Recurse;
  137. }
  138. class SourceMarkerGenerator
  139. {
  140. public:
  141. std::vector<module_context::SourceMarker> markersForCursorLocations(
  142. const std::vector<core::libclang::FileRange>& locations)
  143. {
  144. using namespace module_context;
  145. std::vector<SourceMarker> markers;
  146. for (const libclang::FileRange& loc : locations)
  147. {
  148. FileLocation startLoc = loc.start;
  149. // get file contents and use it to create the message
  150. std::size_t line = startLoc.line - 1;
  151. std::string message;
  152. const std::vector<std::string>& lines = fileContents(
  153. startLoc.filePath.getAbsolutePath());
  154. if (line < lines.size())
  155. message = htmlMessage(loc, lines[line]);
  156. // create marker
  157. SourceMarker marker(SourceMarker::Usage,
  158. startLoc.filePath,
  159. startLoc.line,
  160. startLoc.column,
  161. core::html_utils::HTML(message, true),
  162. true);
  163. // add it to the list
  164. markers.push_back(marker);
  165. }
  166. return markers;
  167. }
  168. private:
  169. static std::string htmlMessage(const libclang::FileRange& loc,
  170. const std::string& message)
  171. {
  172. FileLocation startLoc = loc.start;
  173. FileLocation endLoc = loc.end;
  174. unsigned extent = 0;
  175. if (startLoc.line == endLoc.line)
  176. extent = endLoc.column - startLoc.column;
  177. // attempt to highlight the location
  178. using namespace string_utils;
  179. unsigned col = startLoc.column - 1;
  180. if ((col + extent) < message.length())
  181. {
  182. if (extent == 0)
  183. {
  184. return "<strong>" + htmlEscape(message) + "</strong>";
  185. }
  186. else
  187. {
  188. std::ostringstream ostr;
  189. ostr << htmlEscape(message.substr(0, col));
  190. ostr << "<strong>";
  191. ostr << htmlEscape(message.substr(col, extent));
  192. ostr << "</strong>";
  193. ostr << htmlEscape(message.substr(col + extent));
  194. return ostr.str();
  195. }
  196. }
  197. else
  198. {
  199. return string_utils::htmlEscape(message);
  200. }
  201. }
  202. typedef std::map<std::string,std::vector<std::string> > SourceFileContentsMap;
  203. const std::vector<std::string>& fileContents(const std::string& filename)
  204. {
  205. // check cache
  206. SourceFileContentsMap::const_iterator it =
  207. sourceFileContents_.find(filename);
  208. if (it == sourceFileContents_.end())
  209. {
  210. // check unsaved files
  211. UnsavedFiles& unsavedFiles = rSourceIndex().unsavedFiles();
  212. unsigned numFiles = unsavedFiles.numUnsavedFiles();
  213. for (unsigned i = 0; i<numFiles; ++i)
  214. {
  215. CXUnsavedFile unsavedFile = unsavedFiles.unsavedFilesArray()[i];
  216. if (unsavedFile.Filename != nullptr &&
  217. std::string(unsavedFile.Filename) == filename &&
  218. unsavedFile.Contents != nullptr)
  219. {
  220. std::string contents(unsavedFile.Contents, unsavedFile.Length);
  221. std::vector<std::string> lines;
  222. boost::algorithm::split(lines,
  223. contents,
  224. boost::is_any_of("\n"));
  225. sourceFileContents_.insert(std::make_pair(filename, lines));
  226. it = sourceFileContents_.find(filename);
  227. break;
  228. }
  229. }
  230. // if we didn't get one then read it from disk
  231. if (it == sourceFileContents_.end())
  232. {
  233. std::vector<std::string> lines;
  234. Error error = readStringVectorFromFile(FilePath(filename),
  235. &lines,
  236. false);
  237. if (error)
  238. LOG_ERROR(error);
  239. // insert anyway to ensure it->second below works
  240. sourceFileContents_.insert(std::make_pair(filename, lines));
  241. it = sourceFileContents_.find(filename);
  242. }
  243. }
  244. // return reference to contents
  245. return it->second;
  246. }
  247. private:
  248. SourceFileContentsMap sourceFileContents_;
  249. };
  250. void findReferences(std::string USR,
  251. CXTranslationUnit tu,
  252. std::string* pSpelling,
  253. std::vector<core::libclang::FileRange>* pRefs)
  254. {
  255. FindReferencesData findReferencesData(tu, USR);
  256. libclang::clang().visitChildren(
  257. libclang::clang().getTranslationUnitCursor(tu),
  258. findReferencesVisitor,
  259. (CXClientData)&findReferencesData);
  260. // copy the locations to the out parameter
  261. if (!findReferencesData.spelling.empty())
  262. *pSpelling = findReferencesData.spelling;
  263. std::copy(findReferencesData.references.begin(),
  264. findReferencesData.references.end(),
  265. std::back_inserter(*pRefs));
  266. }
  267. } // anonymous namespace
  268. core::Error findReferences(const core::libclang::FileLocation& location,
  269. std::string* pSpelling,
  270. std::vector<core::libclang::FileRange>* pRefs)
  271. {
  272. Cursor cursor = rSourceIndex().referencedCursorForFileLocation(location);
  273. if (!cursor.isValid() || !cursor.isDeclaration())
  274. return Success();
  275. // get it's USR (bail if it doesn't have one)
  276. std::string USR = cursor.getUSR();
  277. if (USR.empty())
  278. return Success();
  279. // determine what translation units to look in -- if this is a package
  280. // then we look throughout all the source code in the package.
  281. if (rCompilationDatabase().hasTranslationUnit(
  282. location.filePath.getAbsolutePath()))
  283. {
  284. // get all translation units to search
  285. std::vector<std::string> files = rCompilationDatabase()
  286. .translationUnits();
  287. // get translation units we've already indexed
  288. std::map<std::string,TranslationUnit> indexedUnits =
  289. rSourceIndex().getIndexedTranslationUnits();
  290. for (const std::string& filename : files)
  291. {
  292. // first look in already indexed translation units
  293. // (this will pickup unsaved files)
  294. std::map<std::string,TranslationUnit>::iterator it =
  295. indexedUnits.find(filename);
  296. if (it != indexedUnits.end())
  297. {
  298. findReferences(USR,
  299. it->second.getCXTranslationUnit(),
  300. pSpelling,
  301. pRefs);
  302. }
  303. else
  304. {
  305. // get the compilation arguments for this file and use them to
  306. // create a temporary translation unit to search
  307. std::vector<std::string> compileArgs =
  308. rCompilationDatabase().compileArgsForTranslationUnit(filename,
  309. true);
  310. if (compileArgs.empty())
  311. continue;
  312. // create temporary index
  313. CXIndex index = libclang::clang().createIndex(
  314. 1 /* Exclude PCH */,
  315. (rSourceIndex().verbose() > 0) ? 1 : 0);
  316. // get args in form clang expects
  317. core::system::ProcessArgs argsArray(compileArgs);
  318. // parse the translation unit
  319. CXTranslationUnit tu = libclang::clang().parseTranslationUnit(
  320. index,
  321. filename.c_str(),
  322. argsArray.args(),
  323. gsl::narrow_cast<int>(argsArray.argCount()),
  324. nullptr, 0, // no unsaved files
  325. CXTranslationUnit_None |
  326. CXTranslationUnit_Incomplete);
  327. // find references
  328. findReferences(USR, tu, pSpelling, pRefs);
  329. // dispose translation unit and index
  330. libclang::clang().disposeTranslationUnit(tu);
  331. libclang::clang().disposeIndex(index);
  332. }
  333. }
  334. }
  335. // not a package, just search locally
  336. else
  337. {
  338. TranslationUnit tu = rSourceIndex().getTranslationUnit(
  339. location.filePath.getAbsolutePath(),
  340. true);
  341. if (!tu.empty())
  342. findReferences(USR, tu.getCXTranslationUnit(), pSpelling, pRefs);
  343. }
  344. return Success();
  345. }
  346. Error findUsages(const json::JsonRpcRequest& request,
  347. json::JsonRpcResponse* pResponse)
  348. {
  349. // get params
  350. std::string docPath;
  351. int line, column;
  352. Error error = json::readParams(request.params,
  353. &docPath,
  354. &line,
  355. &column);
  356. if (error)
  357. return error;
  358. // resolve the docPath if it's aliased
  359. FilePath filePath = module_context::resolveAliasedPath(docPath);
  360. // get the declaration cursor for this file location
  361. core::libclang::FileLocation location(filePath, line, column);
  362. // find the references
  363. std::string spelling;
  364. std::vector<core::libclang::FileRange> usageLocations;
  365. error = findReferences(location, &spelling, &usageLocations);
  366. if (error)
  367. return error;
  368. // produce source markers from cursor locations
  369. using namespace module_context;
  370. std::vector<SourceMarker> markers = SourceMarkerGenerator()
  371. .markersForCursorLocations(usageLocations);
  372. if (markers.size() > 0)
  373. {
  374. SourceMarkerSet markerSet("C++ Find Usages: " + spelling, markers);
  375. showSourceMarkers(markerSet, MarkerAutoSelectNone);
  376. }
  377. return Success();
  378. }
  379. } // namespace clang
  380. } // namespace modules
  381. } // namespace session
  382. } // namespace rstudio