1 | /**************************************************************************** |
2 | * Copyright (C) 2012-2016 Woboq GmbH |
3 | * Olivier Goffart <contact at woboq.com> |
4 | * https://woboq.com/codebrowser.html |
5 | * |
6 | * This file is part of the Woboq Code Browser. |
7 | * |
8 | * Commercial License Usage: |
9 | * Licensees holding valid commercial licenses provided by Woboq may use |
10 | * this file in accordance with the terms contained in a written agreement |
11 | * between the licensee and Woboq. |
12 | * For further information see https://woboq.com/codebrowser.html |
13 | * |
14 | * Alternatively, this work may be used under a Creative Commons |
15 | * Attribution-NonCommercial-ShareAlike 3.0 (CC-BY-NC-SA 3.0) License. |
16 | * http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US |
17 | * This license does not allow you to use the code browser to assist the |
18 | * development of your commercial software. If you intent to do so, consider |
19 | * purchasing a commercial licence. |
20 | ****************************************************************************/ |
21 | |
22 | #include "annotator.h" |
23 | #include "filesystem.h" |
24 | #include "generator.h" |
25 | #include <clang/AST/ASTContext.h> |
26 | #include <clang/AST/Decl.h> |
27 | #include <clang/AST/DeclBase.h> |
28 | #include <clang/AST/DeclCXX.h> |
29 | #include <clang/AST/DeclTemplate.h> |
30 | #include <clang/AST/Mangle.h> |
31 | #include <clang/AST/PrettyPrinter.h> |
32 | #include <clang/AST/RecordLayout.h> |
33 | #include <clang/Basic/FileManager.h> |
34 | #include <clang/Basic/SourceManager.h> |
35 | #include <clang/Basic/Version.h> |
36 | #include <clang/Lex/Lexer.h> |
37 | #include <clang/Lex/Preprocessor.h> |
38 | #include <clang/Sema/Sema.h> |
39 | #include <clang/Tooling/Tooling.h> |
40 | |
41 | #include <fstream> |
42 | #include <iostream> |
43 | #include <sstream> |
44 | #include <time.h> |
45 | |
46 | #include <llvm/ADT/SmallString.h> |
47 | #include <llvm/Support/FileSystem.h> |
48 | #include <llvm/Support/Path.h> |
49 | #include <llvm/Support/Process.h> |
50 | #include <llvm/Support/raw_ostream.h> |
51 | |
52 | #include "compat.h" |
53 | #include "inlayhintannotator.h" |
54 | #include "projectmanager.h" |
55 | #include "stringbuilder.h" |
56 | |
57 | namespace { |
58 | |
59 | template<class T> |
60 | ssize_t getTypeSize(const T &t) |
61 | { |
62 | const clang::ASTContext &ctx = t->getASTContext(); |
63 | const clang::QualType &ty = ctx.getRecordType(Decl: t); |
64 | |
65 | /** Return size in bytes */ |
66 | return ctx.getTypeSize(T: ty) >> 3; |
67 | } |
68 | |
69 | /** |
70 | * XXX: avoid endless recursion inside |
71 | * clang::ASTContext::getTypeInfo() -> getTypeInfoImpl() |
72 | */ |
73 | template<class T> |
74 | bool cxxDeclIndependent(const T *decl) |
75 | { |
76 | const clang::CXXRecordDecl *cxx = llvm::dyn_cast<clang::CXXRecordDecl>(decl); |
77 | if (cxx && cxx->isDependentContext()) { |
78 | return false; |
79 | } |
80 | |
81 | /** Non CXX always independent */ |
82 | return true; |
83 | } |
84 | |
85 | ssize_t getDeclSize(const clang::Decl *decl) |
86 | { |
87 | const clang::CXXRecordDecl *cxx = llvm::dyn_cast<clang::CXXRecordDecl>(Val: decl); |
88 | if (cxx && (cxx = cxx->getDefinition())) { |
89 | if (!cxxDeclIndependent(decl)) { |
90 | return -1; |
91 | } |
92 | return getTypeSize(t: cxx); |
93 | } |
94 | |
95 | const clang::RecordDecl *c = llvm::dyn_cast<clang::RecordDecl>(Val: decl); |
96 | if (c && (c = c->getDefinition())) { |
97 | return getTypeSize(t: c); |
98 | } |
99 | |
100 | return -1; |
101 | } |
102 | |
103 | ssize_t getFieldOffset(const clang::Decl *decl) |
104 | { |
105 | const clang::FieldDecl *fd = llvm::dyn_cast<clang::FieldDecl>(Val: decl); |
106 | if (!fd || fd->isInvalidDecl()) { |
107 | return -1; |
108 | } |
109 | |
110 | const clang::RecordDecl *parent = fd->getParent(); |
111 | if (!parent || parent->isInvalidDecl() || !cxxDeclIndependent(decl: parent)) { |
112 | return -1; |
113 | } |
114 | |
115 | const clang::ASTRecordLayout &layout = decl->getASTContext().getASTRecordLayout(D: parent); |
116 | return layout.getFieldOffset(FieldNo: fd->getFieldIndex()); |
117 | } |
118 | |
119 | } |
120 | |
121 | Annotator::~Annotator() |
122 | { |
123 | } |
124 | |
125 | Annotator::Visibility Annotator::getVisibility(const clang::NamedDecl *decl) |
126 | { |
127 | if (llvm::isa<clang::EnumConstantDecl>(Val: decl) || llvm::isa<clang::EnumDecl>(Val: decl) |
128 | || llvm::isa<clang::NamespaceDecl>(Val: decl) || llvm::isa<clang::NamespaceAliasDecl>(Val: decl) |
129 | || llvm::isa<clang::TypedefDecl>(Val: decl) || llvm::isa<clang::TypedefNameDecl>(Val: decl)) { |
130 | |
131 | if (!decl->isDefinedOutsideFunctionOrMethod()) |
132 | return Visibility::Local; |
133 | if (decl->isInAnonymousNamespace()) |
134 | return Visibility::Static; |
135 | return Visibility::Global; // FIXME |
136 | } |
137 | |
138 | if (llvm::isa<clang::NonTypeTemplateParmDecl>(Val: decl)) |
139 | return Visibility::Static; |
140 | |
141 | if (llvm::isa<clang::LabelDecl>(Val: decl)) |
142 | return Visibility::Local; |
143 | |
144 | #if CLANG_VERSION_MAJOR >= 5 |
145 | if (llvm::isa<clang::CXXDeductionGuideDecl>(Val: decl)) |
146 | return Visibility::Static; // Because it is not referenced in the AST anyway (FIXME) |
147 | #endif |
148 | |
149 | clang::SourceManager &sm = getSourceMgr(); |
150 | clang::FileID mainFID = sm.getMainFileID(); |
151 | |
152 | switch (decl->getLinkageInternal()) { |
153 | default: |
154 | case clang::NoLinkage: |
155 | return Visibility::Local; |
156 | case clang::ExternalLinkage: |
157 | if (decl->getDeclContext()->isRecord() |
158 | && mainFID |
159 | == sm.getFileID( |
160 | SpellingLoc: sm.getSpellingLoc(Loc: llvm::dyn_cast<clang::NamedDecl>(Val: decl->getDeclContext()) |
161 | ->getCanonicalDecl() |
162 | ->getSourceRange() |
163 | .getBegin()))) { |
164 | // private class |
165 | const clang::CXXMethodDecl *fun = llvm::dyn_cast<clang::CXXMethodDecl>(Val: decl); |
166 | if (fun && fun->isVirtual()) |
167 | return Visibility::Global; // because we need to check overrides |
168 | return Visibility::Static; |
169 | } |
170 | if (decl->isInvalidDecl() && llvm::isa<clang::VarDecl>(Val: decl)) { |
171 | // Avoid polution because of invalid declarations |
172 | return Visibility::Static; |
173 | } |
174 | return Visibility::Global; |
175 | case clang::InternalLinkage: |
176 | if (mainFID != sm.getFileID(SpellingLoc: sm.getSpellingLoc(Loc: decl->getSourceRange().getBegin()))) |
177 | return Visibility::Global; |
178 | return Visibility::Static; |
179 | case clang::UniqueExternalLinkage: |
180 | return Visibility::Static; |
181 | } |
182 | } |
183 | |
184 | bool Annotator::shouldProcess(clang::FileID FID) |
185 | { |
186 | auto it = cache.find(x: FID); |
187 | if (it == cache.end()) { |
188 | htmlNameForFile(id: FID); |
189 | it = cache.find(x: FID); |
190 | assert(it != cache.end()); |
191 | } |
192 | return it->second.first; |
193 | } |
194 | |
195 | |
196 | std::string Annotator::htmlNameForFile(clang::FileID id) |
197 | { |
198 | { |
199 | auto it = cache.find(x: id); |
200 | if (it != cache.end()) { |
201 | return it->second.second; |
202 | } |
203 | } |
204 | |
205 | const clang::FileEntry *entry = getSourceMgr().getFileEntryForID(FID: id); |
206 | if (!entry || llvm::StringRef(entry->getName()).empty()) { |
207 | cache[id] = { false, {} }; |
208 | return {}; |
209 | } |
210 | llvm::SmallString<256> filename; |
211 | canonicalize(path: entry->getName(), result&: filename); |
212 | |
213 | ProjectInfo *project = projectManager.projectForFile(filename); |
214 | if (project) { |
215 | bool should_process = projectManager.shouldProcess(filename, project); |
216 | project_cache[id] = project; |
217 | std::string fn = project->name % "/" % filename.substr(Start: project->source_path.size()); |
218 | cache[id] = { should_process, fn }; |
219 | return fn; |
220 | } |
221 | |
222 | cache[id] = { false, {} }; |
223 | return {}; |
224 | } |
225 | |
226 | static char normalizeForfnIndex(char c) |
227 | { |
228 | if (c >= 'A' && c <= 'Z') |
229 | c = c - 'A' + 'a'; |
230 | if (c < 'a' || c > 'z') |
231 | return '_'; |
232 | return c; |
233 | } |
234 | |
235 | void Annotator::registerInterestingDefinition(clang::SourceRange sourceRange, |
236 | clang::NamedDecl *decl) |
237 | { |
238 | std::string declName = decl->getQualifiedNameAsString(); |
239 | clang::FileID fileId = sourceManager->getFileID(SpellingLoc: sourceRange.getBegin()); |
240 | auto &set = interestingDefinitionsInFile[fileId]; |
241 | set.insert(x: declName); |
242 | } |
243 | |
244 | bool Annotator::generate(clang::Sema &Sema, bool WasInDatabase) |
245 | { |
246 | #if CLANG_VERSION_MAJOR >= 16 |
247 | static const std::string mp_suffix = |
248 | llvm::sys::Process::GetEnv(name: "MULTIPROCESS_MODE" ).value_or(u: "" ); |
249 | #else |
250 | static const std::string mp_suffix = |
251 | llvm::sys::Process::GetEnv("MULTIPROCESS_MODE" ).getValueOr("" ); |
252 | #endif |
253 | |
254 | std::ofstream fileIndex; |
255 | fileIndex.open(s: projectManager.outputPrefix + "/fileIndex" + mp_suffix, mode: std::ios::app); |
256 | if (!fileIndex) { |
257 | create_directories(path: projectManager.outputPrefix); |
258 | fileIndex.open(s: projectManager.outputPrefix + "/fileIndex" + mp_suffix, mode: std::ios::app); |
259 | if (!fileIndex) { |
260 | std::cerr << "Can't generate index for " << std::endl; |
261 | return false; |
262 | } |
263 | } |
264 | |
265 | // make sure the main file is in the cache. |
266 | htmlNameForFile(id: getSourceMgr().getMainFileID()); |
267 | |
268 | std::set<std::string> done; |
269 | for (auto it : cache) { |
270 | if (!it.second.first) |
271 | continue; |
272 | const std::string &fn = it.second.second; |
273 | if (done.count(x: fn)) |
274 | continue; |
275 | done.insert(x: fn); |
276 | |
277 | auto project_it = std::find_if( |
278 | first: projectManager.projects.cbegin(), last: projectManager.projects.cend(), |
279 | pred: [&fn](const ProjectInfo &it) { return llvm::StringRef(fn).startswith(Prefix: it.name); }); |
280 | if (project_it == projectManager.projects.cend()) { |
281 | std::cerr << "GENERATION ERROR: " << fn << " not in a project" << std::endl; |
282 | continue; |
283 | } |
284 | |
285 | clang::FileID FID = it.first; |
286 | |
287 | Generator &g = generator(fid: FID); |
288 | |
289 | syntaxHighlight(generator&: g, FID, Sema); |
290 | // clang::html::HighlightMacros(R, FID, PP); |
291 | |
292 | std::string ; |
293 | clang::FileID mainFID = getSourceMgr().getMainFileID(); |
294 | if (FID != mainFID) { |
295 | footer = "Generated while processing <a href='" % pathTo(From: FID, To: mainFID) % "'>" |
296 | % htmlNameForFile(id: mainFID) % "</a><br/>" ; |
297 | } |
298 | |
299 | auto now = time(timer: 0); |
300 | auto tm = localtime(timer: &now); |
301 | char buf[80]; |
302 | strftime(s: buf, maxsize: sizeof(buf), format: "%Y-%b-%d" , tp: tm); |
303 | |
304 | const ProjectInfo &projectinfo = *project_it; |
305 | footer %= |
306 | "Generated on <em>" % std::string(buf) % "</em>" % " from project " % projectinfo.name; |
307 | if (!projectinfo.revision.empty()) |
308 | footer %= " revision <em>" % projectinfo.revision % "</em>" ; |
309 | |
310 | /* << " from file <a href='" << projectinfo.fileRepoUrl(filename) << "'>" << |
311 | filename << "</a>" title=\"Arguments: << " << Generator::escapeAttr(args) <<"\"" */ |
312 | |
313 | // Emit the HTML. |
314 | #if CLANG_VERSION_MAJOR >= 12 |
315 | const llvm::StringRef Buf = getSourceMgr().getBufferData(FID); |
316 | g.generate(outputPrefix: projectManager.outputPrefix, dataPath: projectManager.dataPath, filename: fn, begin: Buf.begin(), end: Buf.end(), |
317 | footer, |
318 | warningMessage: WasInDatabase ? "" |
319 | : "Warning: That file was not part of the compilation database. " |
320 | "It may have many parsing errors." , |
321 | interestingDefitions: interestingDefinitionsInFile[FID]); |
322 | |
323 | #else |
324 | const llvm::MemoryBuffer *Buf = getSourceMgr().getBuffer(FID); |
325 | g.generate(projectManager.outputPrefix, projectManager.dataPath, fn, Buf->getBufferStart(), |
326 | Buf->getBufferEnd(), footer, |
327 | WasInDatabase ? "" |
328 | : "Warning: That file was not part of the compilation database. " |
329 | "It may have many parsing errors." , |
330 | interestingDefinitionsInFile[FID]); |
331 | |
332 | #endif |
333 | |
334 | if (projectinfo.type == ProjectInfo::Normal) |
335 | fileIndex << fn << '\n'; |
336 | } |
337 | |
338 | // make sure all the docs are in the references |
339 | // (There might not be when the comment is in the .cpp file (for \class)) |
340 | for (auto it : commentHandler.docs) |
341 | references[it.first]; |
342 | |
343 | create_directories(path: llvm::Twine(projectManager.outputPrefix, "/refs/_M" )); |
344 | for (const auto &it : references) { |
345 | if (llvm::StringRef(it.first).startswith(Prefix: "__builtin" )) |
346 | continue; |
347 | if (it.first == "main" ) |
348 | continue; |
349 | |
350 | auto refFilename = it.first; |
351 | replace_invalid_filename_chars(str&: refFilename); |
352 | |
353 | std::string filename = projectManager.outputPrefix % "/refs/" % refFilename % mp_suffix; |
354 | #if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 5 |
355 | std::string error; |
356 | llvm::raw_fd_ostream myfile(filename.c_str(), error, llvm::sys::fs::F_Append); |
357 | if (!error.empty()) { |
358 | std::cerr << error << std::endl; |
359 | continue; |
360 | } |
361 | #else |
362 | std::error_code error_code; |
363 | #if CLANG_VERSION_MAJOR >= 13 |
364 | llvm::raw_fd_ostream myfile(filename, error_code, llvm::sys::fs::OF_Append); |
365 | #else |
366 | llvm::raw_fd_ostream myfile(filename, error_code, llvm::sys::fs::F_Append); |
367 | #endif |
368 | if (error_code) { |
369 | std::cerr << "Error writing ref file " << filename << ": " << error_code.message() |
370 | << std::endl; |
371 | continue; |
372 | } |
373 | #endif |
374 | for (const auto &it2 : it.second) { |
375 | clang::SourceRange loc = it2.loc; |
376 | clang::SourceManager &sm = getSourceMgr(); |
377 | clang::SourceLocation expBegin = sm.getExpansionLoc(Loc: loc.getBegin()); |
378 | clang::SourceLocation expEnd = sm.getExpansionLoc(Loc: loc.getEnd()); |
379 | std::string fn = htmlNameForFile(id: sm.getFileID(SpellingLoc: expBegin)); |
380 | if (fn.empty()) |
381 | continue; |
382 | clang::PresumedLoc fixedBegin = sm.getPresumedLoc(Loc: expBegin); |
383 | clang::PresumedLoc fixedEnd = sm.getPresumedLoc(Loc: expEnd); |
384 | const char *tag = "" ; |
385 | char usetype = '\0'; |
386 | switch (it2.what) { |
387 | case Use: |
388 | case Use_NestedName: |
389 | tag = "use" ; |
390 | break; |
391 | case Use_Address: |
392 | tag = "use" ; |
393 | usetype = 'a'; |
394 | break; |
395 | case Use_Call: |
396 | tag = "use" ; |
397 | usetype = 'c'; |
398 | break; |
399 | case Use_Read: |
400 | tag = "use" ; |
401 | usetype = 'r'; |
402 | break; |
403 | case Use_Write: |
404 | tag = "use" ; |
405 | usetype = 'w'; |
406 | break; |
407 | case Use_MemberAccess: |
408 | tag = "use" ; |
409 | usetype = 'm'; |
410 | break; |
411 | case Declaration: |
412 | tag = "dec" ; |
413 | break; |
414 | case Definition: |
415 | tag = "def" ; |
416 | break; |
417 | case Override: |
418 | tag = "ovr" ; |
419 | break; |
420 | case Inherit: |
421 | tag = "inh" ; |
422 | } |
423 | myfile << "<" << tag << " f='" ; |
424 | Generator::escapeAttr(os&: myfile, s: fn); |
425 | myfile << "' l='" << fixedBegin.getLine() << "'" ; |
426 | if (fixedEnd.isValid() && fixedBegin.getLine() != fixedEnd.getLine()) |
427 | myfile << " ll='" << fixedEnd.getLine() << "'" ; |
428 | if (loc.getBegin().isMacroID()) |
429 | myfile << " macro='1'" ; |
430 | if (!WasInDatabase) |
431 | myfile << " brk='1'" ; |
432 | if (usetype) |
433 | myfile << " u='" << usetype << "'" ; |
434 | const auto &refType = it2.typeOrContext; |
435 | if (!refType.empty()) { |
436 | myfile << ((it2.what < Use) ? " type='" : " c='" ); |
437 | Generator::escapeAttr(os&: myfile, s: refType); |
438 | myfile << "'" ; |
439 | } |
440 | myfile << "/>\n" ; |
441 | } |
442 | auto itS = structure_sizes.find(x: it.first); |
443 | if (itS != structure_sizes.end() && itS->second != -1) { |
444 | myfile << "<size>" << itS->second << "</size>\n" ; |
445 | } |
446 | auto itF = field_offsets.find(x: it.first); |
447 | if (itF != field_offsets.end() && itF->second != -1) { |
448 | myfile << "<offset>" << itF->second << "</offset>\n" ; |
449 | } |
450 | auto range = commentHandler.docs.equal_range(x: it.first); |
451 | for (auto it2 = range.first; it2 != range.second; ++it2) { |
452 | clang::SourceManager &sm = getSourceMgr(); |
453 | clang::SourceLocation exp = sm.getExpansionLoc(Loc: it2->second.loc); |
454 | clang::PresumedLoc fixed = sm.getPresumedLoc(Loc: exp); |
455 | std::string fn = htmlNameForFile(id: sm.getFileID(SpellingLoc: exp)); |
456 | myfile << "<doc f='" ; |
457 | Generator::escapeAttr(os&: myfile, s: fn); |
458 | myfile << "' l='" << fixed.getLine() << "'>" ; |
459 | Generator::escapeAttr(os&: myfile, s: it2->second.content); |
460 | myfile << "</doc>\n" ; |
461 | } |
462 | auto itU = sub_refs.find(x: it.first); |
463 | if (itU != sub_refs.end()) { |
464 | for (const auto &sub : itU->second) { |
465 | switch (sub.what) { |
466 | case SubRef::Function: |
467 | myfile << "<fun " ; |
468 | break; |
469 | case SubRef::Member: |
470 | myfile << "<mbr " ; |
471 | break; |
472 | case SubRef::Static: |
473 | myfile << "<smbr " ; |
474 | break; |
475 | case SubRef::None: |
476 | continue; // should not happen |
477 | } |
478 | const auto &r = sub.ref; |
479 | myfile << "r='" << Generator::EscapeAttr { .value: r } << "'" ; |
480 | auto itF = field_offsets.find(x: r); |
481 | if (itF != field_offsets.end() && itF->second != -1) |
482 | myfile << " o='" << itF->second << "'" ; |
483 | if (!sub.type.empty()) |
484 | myfile << " t='" << Generator::EscapeAttr { .value: sub.type } << "'" ; |
485 | myfile << "/>\n" ; |
486 | } |
487 | } |
488 | } |
489 | |
490 | // now the function names |
491 | create_directories(path: llvm::Twine(projectManager.outputPrefix, "/fnSearch" )); |
492 | for (auto &fnIt : functionIndex) { |
493 | auto fnName = fnIt.first; |
494 | if (fnName.size() < 4) |
495 | continue; |
496 | if (fnName.find(s: "__" ) != std::string::npos) |
497 | continue; // remove internals |
498 | if (fnName.find(c: '<') != std::string::npos || fnName.find(c: '>') != std::string::npos) |
499 | continue; // remove template stuff |
500 | if (fnName == "main" ) |
501 | continue; |
502 | |
503 | llvm::SmallString<8> saved; |
504 | auto pos = fnName.size() + 2; |
505 | int count = 0; |
506 | while (count < 2) { |
507 | count++; |
508 | if (pos < 4) |
509 | break; |
510 | pos = fnName.rfind(s: "::" , pos: pos - 4); |
511 | if (pos >= fnName.size()) { |
512 | pos = 0; |
513 | } else { |
514 | pos += 2; // skip :: |
515 | } |
516 | char idx[3] = { normalizeForfnIndex(c: fnName[pos]), normalizeForfnIndex(c: fnName[pos + 1]), |
517 | '\0' }; |
518 | llvm::StringRef idxRef(idx, 3); // include the '\0' on purpose |
519 | if (saved.find(Str: idxRef) == std::string::npos) { |
520 | std::string funcIndexFN = |
521 | projectManager.outputPrefix % "/fnSearch/" % idx % mp_suffix; |
522 | #if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 5 |
523 | std::string error; |
524 | llvm::raw_fd_ostream funcIndexFile(funcIndexFN.c_str(), error, |
525 | llvm::sys::fs::F_Append); |
526 | if (!error.empty()) { |
527 | std::cerr << error << std::endl; |
528 | return false; |
529 | } |
530 | #else |
531 | std::error_code error_code; |
532 | #if CLANG_VERSION_MAJOR >= 13 |
533 | llvm::raw_fd_ostream funcIndexFile(funcIndexFN, error_code, |
534 | llvm::sys::fs::OF_Append); |
535 | #else |
536 | llvm::raw_fd_ostream funcIndexFile(funcIndexFN, error_code, |
537 | llvm::sys::fs::F_Append); |
538 | #endif |
539 | |
540 | if (error_code) { |
541 | std::cerr << "Error writing index file " << funcIndexFN << ": " |
542 | << error_code.message() << std::endl; |
543 | continue; |
544 | } |
545 | #endif |
546 | funcIndexFile << fnIt.second << '|' << fnIt.first << '\n'; |
547 | saved.append(RHS: idxRef); // include \0; |
548 | } |
549 | } |
550 | } |
551 | return true; |
552 | } |
553 | |
554 | |
555 | std::string Annotator::pathTo(clang::FileID From, clang::FileID To, std::string *dataProj) |
556 | { |
557 | std::string &result = pathTo_cache[{ From.getHashValue(), To.getHashValue() }]; |
558 | if (!result.empty()) { |
559 | if (dataProj) { |
560 | auto pr_it = project_cache.find(x: To); |
561 | if (pr_it != project_cache.end()) { |
562 | if (pr_it->second->type == ProjectInfo::External) { |
563 | *dataProj = pr_it->second->name; |
564 | } |
565 | } |
566 | } |
567 | return result; |
568 | } |
569 | |
570 | std::string fromFN = htmlNameForFile(id: From); |
571 | std::string toFN = htmlNameForFile(id: To); |
572 | |
573 | auto pr_it = project_cache.find(x: To); |
574 | if (pr_it == project_cache.end()) |
575 | return result = {}; |
576 | |
577 | if (pr_it->second->type == ProjectInfo::External) { |
578 | generator(fid: From).addProject(a: pr_it->second->name, b: pr_it->second->external_root_url); |
579 | if (dataProj) { |
580 | *dataProj = pr_it->second->name % "\" " ; |
581 | } |
582 | return result = pr_it->second->external_root_url % "/" % toFN % ".html" ; |
583 | } |
584 | |
585 | return result = naive_uncomplete(base: llvm::sys::path::parent_path(path: fromFN), path: toFN) + ".html" ; |
586 | } |
587 | |
588 | std::string Annotator::pathTo(clang::FileID From, const clang::FileEntry *To) |
589 | { |
590 | // this is a bit duplicated with the other pathTo and htmlNameForFile |
591 | |
592 | if (!To || llvm::StringRef(To->getName()).empty()) |
593 | return {}; |
594 | |
595 | std::string fromFN = htmlNameForFile(id: From); |
596 | |
597 | llvm::SmallString<256> filename; |
598 | canonicalize(path: To->getName(), result&: filename); |
599 | |
600 | |
601 | ProjectInfo *project = projectManager.projectForFile(filename); |
602 | if (!project) |
603 | return {}; |
604 | |
605 | if (project->type == ProjectInfo::External) { |
606 | return project->external_root_url % "/" % project->name % "/" |
607 | % (filename.c_str() + project->source_path.size()) % ".html" ; |
608 | } |
609 | |
610 | return naive_uncomplete( |
611 | base: llvm::sys::path::parent_path(path: fromFN), |
612 | path: std::string(project->name % "/" % (filename.c_str() + project->source_path.size()) |
613 | % ".html" )); |
614 | } |
615 | |
616 | static const clang::Decl *getDefinitionDecl(clang::Decl *decl) |
617 | { |
618 | if (const clang::RecordDecl *rec = llvm::dyn_cast<clang::RecordDecl>(Val: decl)) { |
619 | rec = rec->getDefinition(); |
620 | if (rec) |
621 | return rec; |
622 | } else if (const clang::FunctionDecl *fnc = llvm::dyn_cast<clang::FunctionDecl>(Val: decl)) { |
623 | if (fnc->hasBody(Definition&: fnc) && fnc) { |
624 | return fnc; |
625 | } |
626 | } |
627 | return decl->getCanonicalDecl(); |
628 | } |
629 | |
630 | void Annotator::registerReference(clang::NamedDecl *decl, clang::SourceRange range, |
631 | Annotator::TokenType type, Annotator::DeclType declType, |
632 | std::string typeText, clang::NamedDecl *usedContext) |
633 | { |
634 | // annonymouse namespace, anonymous struct, or unnamed argument. |
635 | if (decl->getDeclName().isIdentifier() && decl->getName().empty()) |
636 | return; |
637 | |
638 | clang::SourceManager &sm = getSourceMgr(); |
639 | |
640 | Visibility visibility = getVisibility(decl); |
641 | |
642 | // Interesting definitions |
643 | if (declType == Annotator::Definition && visibility == Visibility::Global) { |
644 | if (llvm::isa<clang::TagDecl>(Val: decl) |
645 | || (llvm::isa<clang::FunctionDecl>(Val: decl) && decl->getDeclName().isIdentifier() |
646 | && decl->getName() == "main" )) { |
647 | if (decl->getDeclContext()->isNamespace() |
648 | || decl->getDeclContext()->isTranslationUnit()) { |
649 | registerInterestingDefinition(sourceRange: range, decl); |
650 | } |
651 | } |
652 | } |
653 | |
654 | // When the end location is invalid, this is a virtual range with no matching tokens |
655 | // (eg implicit conversion) |
656 | bool isVirtualLocation = range.getEnd().isInvalid(); |
657 | if (isVirtualLocation) |
658 | range = range.getBegin(); |
659 | |
660 | if (!range.getBegin().isFileID()) { // macro expension. |
661 | clang::SourceLocation expensionloc = sm.getExpansionLoc(Loc: range.getBegin()); |
662 | clang::FileID FID = sm.getFileID(SpellingLoc: expensionloc); |
663 | if (!shouldProcess(FID) |
664 | || sm.getMacroArgExpandedLocation(Loc: range.getBegin()) |
665 | != sm.getMacroArgExpandedLocation(Loc: range.getEnd())) { |
666 | return; |
667 | } |
668 | |
669 | clang::SourceLocation spel1 = sm.getSpellingLoc(Loc: range.getBegin()); |
670 | clang::SourceLocation spel2 = sm.getSpellingLoc(Loc: range.getEnd()); |
671 | if (sm.getFileID(SpellingLoc: spel1) != FID || sm.getFileID(SpellingLoc: spel2) != FID) { |
672 | |
673 | if (visibility == Visibility::Global) { |
674 | if (usedContext && typeText.empty() && declType >= Use) { |
675 | typeText = getContextStr(usedContext); |
676 | } |
677 | addReference(ref: getReferenceAndTitle(decl).first, refLoc: range, type, dt: declType, typeRef: typeText, |
678 | decl); |
679 | } |
680 | return; |
681 | } |
682 | |
683 | range = { spel1, spel2 }; |
684 | } |
685 | clang::FileID FID = sm.getFileID(SpellingLoc: range.getBegin()); |
686 | |
687 | if (!isVirtualLocation && FID != sm.getFileID(SpellingLoc: range.getEnd())) |
688 | return; |
689 | |
690 | if (!shouldProcess(FID)) |
691 | return; |
692 | |
693 | std::string tags; |
694 | std::string clas = computeClas(decl); |
695 | std::string ref; |
696 | |
697 | const clang::Decl *canonDecl = decl->getCanonicalDecl(); |
698 | if (type != Namespace) { |
699 | if (visibility == Visibility::Local) { |
700 | if (!decl->getDeclName().isIdentifier()) |
701 | return; // skip local operators (FIXME) |
702 | |
703 | clang::SourceLocation loc = canonDecl->getLocation(); |
704 | int &id = localeNumbers[loc.getRawEncoding()]; |
705 | if (id == 0) |
706 | id = localeNumbers.size(); |
707 | llvm::StringRef name = decl->getName(); |
708 | ref = (llvm::Twine(id) + name).str(); |
709 | if (type != Label) { |
710 | llvm::SmallString<64> buffer; |
711 | tags %= " title='" % Generator::escapeAttr(name, buffer) % "'" ; |
712 | clas %= " local col" % llvm::Twine(id % 10).str(); |
713 | } |
714 | } else { |
715 | auto cached = getReferenceAndTitle(decl); |
716 | ref = cached.first; |
717 | tags %= " title='" % cached.second % "'" ; |
718 | } |
719 | |
720 | if (visibility == Visibility::Global && type != Typedef) { |
721 | if (usedContext && typeText.empty() && declType >= Use) { |
722 | typeText = getContextStr(usedContext); |
723 | } |
724 | |
725 | clang::SourceRange definitionRange = range; |
726 | if (declType == Definition) |
727 | definitionRange = decl->getSourceRange(); |
728 | addReference(ref, refLoc: definitionRange, type, dt: declType, typeRef: typeText, decl); |
729 | |
730 | if (declType == Definition && ref.find(c: '{') >= ref.size()) { |
731 | if (clang::FunctionDecl *fun = llvm::dyn_cast<clang::FunctionDecl>(Val: decl)) { |
732 | functionIndex.insert(x: { fun->getQualifiedNameAsString(), ref }); |
733 | } |
734 | } |
735 | } else { |
736 | if (!typeText.empty()) { |
737 | llvm::SmallString<64> buffer; |
738 | tags %= " data-type='" % Generator::escapeAttr(typeText, buffer) % "'" ; |
739 | } |
740 | } |
741 | |
742 | if (visibility == Visibility::Static) { |
743 | if (declType < Use) { |
744 | commentHandler.decl_offsets.insert( |
745 | x: { decl->getSourceRange().getBegin(), { ref, false } }); |
746 | } else |
747 | switch (+declType) { |
748 | case Use_Address: |
749 | tags %= " data-use='a'" ; |
750 | break; |
751 | case Use_Read: |
752 | tags %= " data-use='r'" ; |
753 | break; |
754 | case Use_Write: |
755 | tags %= " data-use='w'" ; |
756 | break; |
757 | case Use_Call: |
758 | tags %= " data-use='c'" ; |
759 | break; |
760 | case Use_MemberAccess: |
761 | tags %= " data-use='m'" ; |
762 | break; |
763 | } |
764 | |
765 | clas += " tu" ; |
766 | } |
767 | } |
768 | |
769 | switch (type) { |
770 | case Ref: |
771 | clas += " ref" ; |
772 | break; |
773 | case Member: |
774 | clas += " member" ; |
775 | break; |
776 | case Type: |
777 | clas += " type" ; |
778 | break; |
779 | case Typedef: |
780 | clas += " typedef" ; |
781 | break; |
782 | case Decl: |
783 | clas += " decl" ; |
784 | break; |
785 | case Call: |
786 | clas += " call" ; |
787 | break; |
788 | case Namespace: |
789 | clas += " namespace" ; |
790 | break; |
791 | case Enum: // fall through |
792 | case EnumDecl: |
793 | clas += " enum" ; |
794 | break; |
795 | case Label: |
796 | clas += " lbl" ; |
797 | break; |
798 | } |
799 | |
800 | if (declType == Definition && visibility != Visibility::Local) { |
801 | clas += " def" ; |
802 | } |
803 | |
804 | if (llvm::isa<clang::FunctionDecl>(Val: decl)) { |
805 | clas += " fn" ; |
806 | } else if (llvm::isa<clang::FieldDecl>(Val: decl)) { |
807 | clas += " field" ; |
808 | } |
809 | |
810 | // const llvm::MemoryBuffer *Buf = sm.getBuffer(FID); |
811 | clang::SourceLocation B = range.getBegin(); |
812 | clang::SourceLocation E = isVirtualLocation ? B : range.getEnd(); |
813 | |
814 | int pos = sm.getFileOffset(SpellingLoc: B); |
815 | int len = sm.getFileOffset(SpellingLoc: E) - pos; |
816 | |
817 | if (!isVirtualLocation) { |
818 | // Include the whole end token in the range. |
819 | len += clang::Lexer::MeasureTokenLength(Loc: E, SM: sm, LangOpts: getLangOpts()); |
820 | } else { |
821 | clas += " fake" ; |
822 | } |
823 | |
824 | canonDecl = getDefinitionDecl(decl); |
825 | |
826 | if (clas[0] == ' ') |
827 | clas = clas.substr(pos: 1); |
828 | |
829 | if (ref.empty()) { |
830 | generator(fid: FID).addTag(name: "span" , attributes: "class=\"" % clas % "\"" , pos, len); |
831 | return; |
832 | } |
833 | |
834 | llvm::SmallString<64> escapedRefBuffer; |
835 | auto escapedRef = Generator::escapeAttr(ref, buffer&: escapedRefBuffer); |
836 | tags %= " data-ref=\"" % escapedRef % "\"" ; |
837 | |
838 | if (declType >= Annotator::Use || (decl != canonDecl && declType != Annotator::Definition)) { |
839 | std::string link; |
840 | clang::SourceLocation loc = canonDecl->getLocation(); |
841 | clang::FileID declFID = sm.getFileID(SpellingLoc: sm.getExpansionLoc(Loc: loc)); |
842 | if (declFID != FID) { |
843 | std::string dataProj; |
844 | link = pathTo(From: FID, To: declFID, dataProj: &dataProj); |
845 | |
846 | if (!dataProj.empty()) { |
847 | tags %= " data-proj=\"" % dataProj % "\"" ; |
848 | } |
849 | |
850 | if (declType < Annotator::Use) { |
851 | tags %= " id=\"" % escapedRef % "\"" ; |
852 | } |
853 | |
854 | if (link.empty()) { |
855 | generator(fid: FID).addTag(name: declType >= Annotator::Use ? "span" : "dfn" , |
856 | attributes: "class=\'" % clas % "\'" % tags, pos, len); |
857 | return; |
858 | } |
859 | } |
860 | llvm::SmallString<6> locBuffer; |
861 | link %= "#" |
862 | % (loc.isFileID() && !decl->isImplicit() |
863 | ? escapedRef |
864 | : llvm::Twine(sm.getExpansionLineNumber(Loc: loc)).toStringRef(Out&: locBuffer)); |
865 | std::string tag = "class=\"" % clas % "\" href=\"" % link % "\"" % tags; |
866 | generator(fid: FID).addTag(name: "a" , attributes: tag, pos, len); |
867 | } else { |
868 | std::string tag = "class=\"" % clas % "\" id=\"" % escapedRef % "\"" % tags; |
869 | generator(fid: FID).addTag(name: "dfn" , attributes: tag, pos, len); |
870 | } |
871 | } |
872 | |
873 | void Annotator::addReference(const std::string &ref, clang::SourceRange refLoc, TokenType type, |
874 | DeclType dt, const std::string &typeRef, clang::Decl *decl) |
875 | { |
876 | if (type == Ref || type == Member || type == Decl || type == Call || type == EnumDecl |
877 | || (type == Type && dt != Use_NestedName && dt != Declaration) |
878 | || (type == Enum && dt == Definition)) { |
879 | ssize_t size = getDeclSize(decl); |
880 | if (size >= 0) { |
881 | structure_sizes[ref] = size; |
882 | } |
883 | references[ref].push_back(x: { .what: dt, .loc: refLoc, .typeOrContext: typeRef }); |
884 | if (dt < Use) { |
885 | ssize_t offset = getFieldOffset(decl); |
886 | if (offset >= 0) { |
887 | field_offsets[ref] = offset; |
888 | } |
889 | clang::FullSourceLoc fulloc(decl->getSourceRange().getBegin(), getSourceMgr()); |
890 | commentHandler.decl_offsets.insert(x: { fulloc.getSpellingLoc(), { ref, true } }); |
891 | if (auto parentStruct = llvm::dyn_cast<clang::RecordDecl>(Val: decl->getDeclContext())) { |
892 | auto parentRef = getReferenceAndTitle(decl: parentStruct).first; |
893 | if (!parentRef.empty()) { |
894 | SubRef sr; |
895 | sr.ref = ref; |
896 | if (decl->isFunctionOrFunctionTemplate()) |
897 | sr.what = SubRef::Function; |
898 | else if (llvm::isa<clang::FieldDecl>(Val: decl)) |
899 | sr.what = SubRef::Member; |
900 | else if (llvm::isa<clang::VarDecl>(Val: decl)) |
901 | sr.what = SubRef::Static; |
902 | if (sr.what != SubRef::Function) |
903 | sr.type = typeRef; |
904 | sub_refs[parentRef].push_back(x: sr); |
905 | } |
906 | } |
907 | } |
908 | } |
909 | } |
910 | |
911 | void Annotator::registerOverride(clang::NamedDecl *decl, clang::NamedDecl *overrided, |
912 | clang::SourceLocation loc) |
913 | { |
914 | clang::SourceManager &sm = getSourceMgr(); |
915 | clang::SourceLocation expensionloc = sm.getExpansionLoc(Loc: loc); |
916 | clang::FileID FID = sm.getFileID(SpellingLoc: expensionloc); |
917 | if (!shouldProcess(FID)) |
918 | return; |
919 | if (getVisibility(decl: overrided) != Visibility::Global) |
920 | return; |
921 | |
922 | auto ovrRef = getReferenceAndTitle(decl: overrided).first; |
923 | auto declRef = getReferenceAndTitle(decl).first; |
924 | references[ovrRef].push_back(x: { .what: Override, .loc: expensionloc, .typeOrContext: declRef }); |
925 | |
926 | // Register the reversed relation. |
927 | clang::SourceLocation ovrLoc = sm.getExpansionLoc(Loc: getDefinitionDecl(decl: overrided)->getLocation()); |
928 | references[declRef].push_back(x: { .what: Inherit, .loc: ovrLoc, .typeOrContext: ovrRef }); |
929 | } |
930 | |
931 | void Annotator::registerMacro(const std::string &ref, clang::SourceLocation refLoc, |
932 | DeclType declType) |
933 | { |
934 | references[ref].push_back(x: { .what: declType, .loc: refLoc, .typeOrContext: std::string() }); |
935 | if (declType == Annotator::Declaration) { |
936 | commentHandler.decl_offsets.insert(x: { refLoc, { ref, true } }); |
937 | } |
938 | } |
939 | |
940 | void Annotator::annotateSourceRange(clang::SourceRange range, std::string tag, |
941 | std::string attributes) |
942 | { |
943 | clang::SourceManager &sm = getSourceMgr(); |
944 | if (!range.getBegin().isFileID()) { |
945 | range = sm.getSpellingLoc(Loc: range.getBegin()); |
946 | if (!range.getBegin().isFileID()) |
947 | return; |
948 | } |
949 | |
950 | clang::FileID FID = sm.getFileID(SpellingLoc: range.getBegin()); |
951 | if (FID != sm.getFileID(SpellingLoc: range.getEnd())) { |
952 | return; |
953 | } |
954 | if (!shouldProcess(FID)) |
955 | return; |
956 | |
957 | clang::SourceLocation B = range.getBegin(); |
958 | clang::SourceLocation E = range.getEnd(); |
959 | |
960 | unsigned int pos = sm.getFileOffset(SpellingLoc: B); |
961 | int len = sm.getFileOffset(SpellingLoc: E) - pos; |
962 | |
963 | // Include the whole end token in the range. |
964 | len += clang::Lexer::MeasureTokenLength(Loc: E, SM: sm, LangOpts: getLangOpts()); |
965 | |
966 | generator(fid: FID).addTag(name: std::move(tag), attributes: std::move(attributes), pos, len); |
967 | } |
968 | |
969 | void Annotator::reportDiagnostic(clang::SourceRange range, const std::string &msg, |
970 | const std::string &clas) |
971 | { |
972 | llvm::SmallString<64> buffer; |
973 | annotateSourceRange( |
974 | range, tag: "span" , attributes: "class='" % clas % "' title=\"" % Generator::escapeAttr(msg, buffer) % "\"" ); |
975 | } |
976 | |
977 | void Annotator::addInlayHint(clang::SourceLocation loc, std::string inlayHint) |
978 | { |
979 | clang::FileID FID = getSourceMgr().getFileID(SpellingLoc: loc); |
980 | if (inlayHint.empty() || !shouldProcess(FID)) |
981 | return; |
982 | unsigned int pos = getSourceMgr().getFileOffset(SpellingLoc: loc); |
983 | generator(fid: FID).addTag(name: "span" , attributes: "class='inlayHint'" , pos, len: 0, innerHtml: std::move(inlayHint)); |
984 | } |
985 | |
986 | // basically loosely inspired from clang_getSpecializedCursorTemplate |
987 | static clang::NamedDecl *getSpecializedCursorTemplate(clang::NamedDecl *D) |
988 | { |
989 | using namespace clang; |
990 | using namespace llvm; |
991 | NamedDecl *Template = 0; |
992 | if (CXXRecordDecl *CXXRecord = dyn_cast<CXXRecordDecl>(Val: D)) { |
993 | ClassTemplateDecl *CXXRecordT = 0; |
994 | if (ClassTemplatePartialSpecializationDecl *PartialSpec = |
995 | dyn_cast<ClassTemplatePartialSpecializationDecl>(Val: CXXRecord)) |
996 | CXXRecordT = PartialSpec->getSpecializedTemplate(); |
997 | else if (ClassTemplateSpecializationDecl *ClassSpec = |
998 | dyn_cast<ClassTemplateSpecializationDecl>(Val: CXXRecord)) { |
999 | llvm::PointerUnion<ClassTemplateDecl *, ClassTemplatePartialSpecializationDecl *> |
1000 | Result = ClassSpec->getSpecializedTemplateOrPartial(); |
1001 | if (Result.is<ClassTemplateDecl *>()) |
1002 | CXXRecordT = Result.get<ClassTemplateDecl *>(); |
1003 | else |
1004 | D = CXXRecord = Result.get<ClassTemplatePartialSpecializationDecl *>(); |
1005 | } |
1006 | if (CXXRecordT) |
1007 | D = CXXRecord = CXXRecordT->getTemplatedDecl(); |
1008 | Template = CXXRecord->getInstantiatedFromMemberClass(); |
1009 | } else if (FunctionDecl *Function = dyn_cast<FunctionDecl>(Val: D)) { |
1010 | FunctionTemplateDecl *FunctionT = Function->getPrimaryTemplate(); |
1011 | if (FunctionT) { |
1012 | if (auto Ins = FunctionT->getInstantiatedFromMemberTemplate()) |
1013 | FunctionT = Ins; |
1014 | D = Function = FunctionT->getTemplatedDecl(); |
1015 | } |
1016 | Template = Function->getInstantiatedFromMemberFunction(); |
1017 | } else if (VarDecl *Var = dyn_cast<VarDecl>(Val: D)) { |
1018 | if (Var->isStaticDataMember()) |
1019 | Template = Var->getInstantiatedFromStaticDataMember(); |
1020 | } else if (RedeclarableTemplateDecl *Tmpl = dyn_cast<RedeclarableTemplateDecl>(Val: D)) { |
1021 | Template = Tmpl->getInstantiatedFromMemberTemplate(); |
1022 | } |
1023 | |
1024 | if (Template) |
1025 | return Template; |
1026 | else |
1027 | return D; |
1028 | } |
1029 | |
1030 | static std::string getQualifiedName(clang::NamedDecl *decl) |
1031 | { |
1032 | if (decl->getDeclName()) { |
1033 | return decl->getQualifiedNameAsString(); |
1034 | } |
1035 | // anonymous struct / union |
1036 | std::string name; |
1037 | clang::PrintingPolicy policy(decl->getASTContext().getLangOpts()); |
1038 | // we don't want filelocation:line:column |
1039 | policy.AnonymousTagLocations = false; |
1040 | llvm::raw_string_ostream stream(name); |
1041 | decl->printQualifiedName(OS&: stream, Policy: policy); |
1042 | return name; |
1043 | } |
1044 | |
1045 | |
1046 | std::pair<std::string, std::string> Annotator::getReferenceAndTitle(clang::NamedDecl *decl) |
1047 | { |
1048 | clang::Decl *canonDecl = decl->getCanonicalDecl(); |
1049 | auto &cached = mangle_cache[canonDecl]; |
1050 | if (cached.first.empty()) { |
1051 | decl = getSpecializedCursorTemplate(D: decl); |
1052 | |
1053 | std::string qualName = getQualifiedName(decl); |
1054 | if (llvm::isa<clang::FunctionDecl>(Val: decl) |
1055 | #if CLANG_VERSION_MAJOR >= 5 |
1056 | // We can't mangle a deduction guide (also there is no need since it is not referenced) |
1057 | && !llvm::isa<clang::CXXDeductionGuideDecl>(Val: decl) |
1058 | #endif |
1059 | && mangle->shouldMangleDeclName(D: decl) |
1060 | // workaround crash in clang while trying to mangle some builtin types |
1061 | && !llvm::StringRef(qualName).startswith(Prefix: "__" )) { |
1062 | llvm::raw_string_ostream s(cached.first); |
1063 | if (llvm::isa<clang::CXXDestructorDecl>(Val: decl)) { |
1064 | #if CLANG_VERSION_MAJOR >= 11 |
1065 | mangle->mangleName(GD: clang::GlobalDecl(llvm::cast<clang::CXXDestructorDecl>(Val: decl), |
1066 | clang::Dtor_Complete), |
1067 | s); |
1068 | #else |
1069 | mangle->mangleCXXDtor(llvm::cast<clang::CXXDestructorDecl>(decl), |
1070 | clang::Dtor_Complete, s); |
1071 | #endif |
1072 | } else if (llvm::isa<clang::CXXConstructorDecl>(Val: decl)) { |
1073 | #if CLANG_VERSION_MAJOR >= 11 |
1074 | mangle->mangleName(GD: clang::GlobalDecl(llvm::cast<clang::CXXConstructorDecl>(Val: decl), |
1075 | clang::Ctor_Complete), |
1076 | s); |
1077 | #else |
1078 | mangle->mangleCXXCtor(llvm::cast<clang::CXXConstructorDecl>(decl), |
1079 | clang::Ctor_Complete, s); |
1080 | #endif |
1081 | } else { |
1082 | mangle->mangleName(GD: decl, s); |
1083 | } |
1084 | |
1085 | #ifdef _WIN32 |
1086 | s.flush(); |
1087 | |
1088 | const char *mangledName = cached.first.data(); |
1089 | if (mangledName[0] == 1) { |
1090 | if (mangledName[1] == '_' || mangledName[1] == '?') { |
1091 | if (mangledName[2] == '?') { |
1092 | cached.first = cached.first.substr(3); |
1093 | } else { |
1094 | cached.first = cached.first.substr(2); |
1095 | } |
1096 | } |
1097 | } |
1098 | #endif |
1099 | } else if (clang::FieldDecl *d = llvm::dyn_cast<clang::FieldDecl>(Val: decl)) { |
1100 | cached.first = |
1101 | getReferenceAndTitle(decl: d->getParent()).first + "::" + decl->getName().str(); |
1102 | } else { |
1103 | cached.first = qualName; |
1104 | cached.first.erase(first: std::remove(first: cached.first.begin(), last: cached.first.end(), value: ' '), |
1105 | last: cached.first.end()); |
1106 | // replace < and > because alse jquery can't match them. |
1107 | std::replace(first: cached.first.begin(), last: cached.first.end(), old_value: '<', new_value: '{'); |
1108 | std::replace(first: cached.first.begin(), last: cached.first.end(), old_value: '>', new_value: '}'); |
1109 | } |
1110 | llvm::SmallString<64> buffer; |
1111 | cached.second = std::string(Generator::escapeAttr(qualName, buffer)); |
1112 | |
1113 | if (cached.first.size() > 170) { |
1114 | // If the name is too big, truncate it and add the hash at the end. |
1115 | auto hash = std::hash<std::string>()(cached.first) & 0x00ffffff; |
1116 | cached.first.resize(n: 150); |
1117 | buffer.clear(); |
1118 | cached.first += llvm::Twine(hash).toStringRef(Out&: buffer); |
1119 | } |
1120 | } |
1121 | return cached; |
1122 | } |
1123 | |
1124 | |
1125 | std::string Annotator::getTypeRef(clang::QualType type) |
1126 | { |
1127 | return type.getAsString(Policy: getLangOpts()); |
1128 | } |
1129 | |
1130 | std::string Annotator::getContextStr(clang::NamedDecl *usedContext) |
1131 | { |
1132 | clang::FunctionDecl *fun = llvm::dyn_cast<clang::FunctionDecl>(Val: usedContext); |
1133 | clang::DeclContext *context = usedContext->getDeclContext(); |
1134 | while (!fun && context) { |
1135 | fun = llvm::dyn_cast<clang::FunctionDecl>(Val: context); |
1136 | if (fun && !fun->isDefinedOutsideFunctionOrMethod()) |
1137 | fun = nullptr; |
1138 | context = context->getParent(); |
1139 | } |
1140 | if (fun) |
1141 | return getReferenceAndTitle(decl: fun).first; |
1142 | return {}; |
1143 | } |
1144 | |
1145 | std::string Annotator::getVisibleRef(clang::NamedDecl *Decl) |
1146 | { |
1147 | if (getVisibility(decl: Decl) != Visibility::Global) |
1148 | return {}; |
1149 | return getReferenceAndTitle(decl: Decl).first; |
1150 | } |
1151 | |
1152 | // return the classes to add in the span |
1153 | std::string Annotator::computeClas(clang::NamedDecl *decl) |
1154 | { |
1155 | std::string s; |
1156 | if (clang::CXXMethodDecl *f = llvm::dyn_cast<clang::CXXMethodDecl>(Val: decl)) { |
1157 | if (f->isVirtual()) |
1158 | s = "virtual" ; |
1159 | } |
1160 | return s; |
1161 | } |
1162 | |
1163 | |
1164 | /* This function is inspired From clang::html::SyntaxHighlight() from HTMLRewrite.cpp |
1165 | * from the clang 3.1 from The LLVM Compiler Infrastructure |
1166 | * distributed under the University of Illinois Open Source |
1167 | * Adapted to the codebrowser generator. Also used to parse the comments. |
1168 | * The tags names have been changed, and we make a difference between different kinds of |
1169 | * keywords |
1170 | */ |
1171 | void Annotator::syntaxHighlight(Generator &generator, clang::FileID FID, clang::Sema &Sema) |
1172 | { |
1173 | using namespace clang; |
1174 | |
1175 | const clang::Preprocessor &PP = Sema.getPreprocessor(); |
1176 | const clang::SourceManager &SM = getSourceMgr(); |
1177 | #if CLANG_VERSION_MAJOR >= 16 |
1178 | const auto FromFile = SM.getBufferOrNone(FID); |
1179 | if (!FromFile.has_value()) { |
1180 | return; |
1181 | } |
1182 | Lexer L(FID, *FromFile, SM, getLangOpts()); |
1183 | #elif CLANG_VERSION_MAJOR >= 12 |
1184 | const llvm::Optional<llvm::MemoryBufferRef> FromFile = SM.getBufferOrNone(FID); |
1185 | if (!FromFile.hasValue()) { |
1186 | return; |
1187 | } |
1188 | Lexer L(FID, FromFile.getValue(), SM, getLangOpts()); |
1189 | #else |
1190 | const llvm::MemoryBuffer *FromFile = SM.getBuffer(FID); |
1191 | Lexer L(FID, FromFile, SM, getLangOpts()); |
1192 | #endif |
1193 | const char *BufferStart = FromFile->getBufferStart(); |
1194 | const char *BufferEnd = FromFile->getBufferEnd(); |
1195 | |
1196 | // Inform the preprocessor that we want to retain comments as tokens, so we |
1197 | // can highlight them. |
1198 | L.SetCommentRetentionState(true); |
1199 | |
1200 | // Lex all the tokens in raw mode, to avoid entering #includes or expanding |
1201 | // macros. |
1202 | Token Tok; |
1203 | L.LexFromRawLexer(Result&: Tok); |
1204 | |
1205 | while (Tok.isNot(K: tok::eof)) { |
1206 | // Since we are lexing unexpanded tokens, all tokens are from the main |
1207 | // FileID. |
1208 | unsigned TokOffs = SM.getFileOffset(SpellingLoc: Tok.getLocation()); |
1209 | unsigned TokLen = Tok.getLength(); |
1210 | switch (Tok.getKind()) { |
1211 | default: |
1212 | break; |
1213 | case tok::identifier: |
1214 | llvm_unreachable("tok::identifier in raw lexing mode!" ); |
1215 | case tok::raw_identifier: { |
1216 | // Fill in Result.IdentifierInfo and update the token kind, |
1217 | // looking up the identifier in the identifier table. |
1218 | PP.LookUpIdentifierInfo(Identifier&: Tok); |
1219 | // If this is a pp-identifier, for a keyword, highlight it as such. |
1220 | switch (Tok.getKind()) { |
1221 | case tok::identifier: |
1222 | break; |
1223 | |
1224 | case tok::kw_auto: |
1225 | case tok::kw_char: |
1226 | case tok::kw_const: |
1227 | case tok::kw_double: |
1228 | case tok::kw_float: |
1229 | case tok::kw_int: |
1230 | case tok::kw_long: |
1231 | case tok::kw_register: |
1232 | // case tok::kw_restrict: // ??? (type or not) |
1233 | case tok::kw_short: |
1234 | case tok::kw_signed: |
1235 | case tok::kw_static: |
1236 | case tok::kw_unsigned: |
1237 | case tok::kw_void: |
1238 | case tok::kw_volatile: |
1239 | case tok::kw_bool: |
1240 | case tok::kw_mutable: |
1241 | case tok::kw_wchar_t: |
1242 | case tok::kw_char16_t: |
1243 | case tok::kw_char32_t: |
1244 | generator.addTag(name: "em" , attributes: {}, pos: TokOffs, len: TokLen); |
1245 | break; |
1246 | default: // other keywords |
1247 | generator.addTag(name: "b" , attributes: {}, pos: TokOffs, len: TokLen); |
1248 | } |
1249 | break; |
1250 | } |
1251 | case tok::comment: { |
1252 | unsigned int = TokOffs; |
1253 | unsigned int = TokLen; |
1254 | bool startOfLine = Tok.isAtStartOfLine(); |
1255 | SourceLocation = Tok.getLocation(); |
1256 | L.LexFromRawLexer(Result&: Tok); |
1257 | // Merge consecutive comments |
1258 | if (startOfLine /*&& BufferStart[CommentBegin+1] == '/'*/) { |
1259 | while (Tok.is(K: tok::comment)) { |
1260 | unsigned int Off = SM.getFileOffset(SpellingLoc: Tok.getLocation()); |
1261 | if (BufferStart[Off + 1] != '/') |
1262 | break; |
1263 | CommentLen = Off + Tok.getLength() - CommentBegin; |
1264 | L.LexFromRawLexer(Result&: Tok); |
1265 | } |
1266 | } |
1267 | |
1268 | std::string attributes; |
1269 | |
1270 | if (startOfLine) { |
1271 | unsigned int = SM.getFileOffset(SpellingLoc: Tok.getLocation()); |
1272 | // Find the location of the next \n |
1273 | const char *nl_it = BufferStart + NonCommentBegin; |
1274 | while (nl_it < BufferEnd && *nl_it && *nl_it != '\n') |
1275 | ++nl_it; |
1276 | commentHandler.handleComment( |
1277 | A&: *this, generator, Sema, bufferStart: BufferStart, commentStart: CommentBegin, len: CommentLen, |
1278 | searchLocBegin: Tok.getLocation(), |
1279 | searchLocEnd: Tok.getLocation().getLocWithOffset(Offset: nl_it - (BufferStart + NonCommentBegin)), |
1280 | commentLoc: CommentBeginLocation); |
1281 | } else { |
1282 | // look up the location before |
1283 | const char *nl_it = BufferStart + CommentBegin; |
1284 | while (nl_it > BufferStart && *nl_it && *nl_it != '\n') |
1285 | --nl_it; |
1286 | commentHandler.handleComment( |
1287 | A&: *this, generator, Sema, bufferStart: BufferStart, commentStart: CommentBegin, len: CommentLen, |
1288 | searchLocBegin: CommentBeginLocation.getLocWithOffset(Offset: nl_it - (BufferStart + CommentBegin)), |
1289 | searchLocEnd: CommentBeginLocation, commentLoc: CommentBeginLocation); |
1290 | } |
1291 | continue; // Don't skip next token |
1292 | } |
1293 | case tok::utf8_string_literal: |
1294 | // Chop off the u part of u8 prefix |
1295 | ++TokOffs; |
1296 | --TokLen; |
1297 | LLVM_FALLTHROUGH; |
1298 | case tok::wide_string_literal: |
1299 | case tok::utf16_string_literal: |
1300 | case tok::utf32_string_literal: |
1301 | // Chop off the L, u, U or 8 prefix |
1302 | ++TokOffs; |
1303 | --TokLen; |
1304 | LLVM_FALLTHROUGH; |
1305 | case tok::string_literal: |
1306 | // FIXME: Exclude the optional ud-suffix from the highlighted range. |
1307 | generator.addTag(name: "q" , attributes: {}, pos: TokOffs, len: TokLen); |
1308 | break; |
1309 | |
1310 | case tok::wide_char_constant: |
1311 | case tok::utf16_char_constant: |
1312 | case tok::utf32_char_constant: |
1313 | ++TokOffs; |
1314 | --TokLen; |
1315 | LLVM_FALLTHROUGH; |
1316 | case tok::char_constant: |
1317 | generator.addTag(name: "kbd" , attributes: {}, pos: TokOffs, len: TokLen); |
1318 | break; |
1319 | case tok::numeric_constant: |
1320 | generator.addTag(name: "var" , attributes: {}, pos: TokOffs, len: TokLen); |
1321 | break; |
1322 | case tok::hash: { |
1323 | // If this is a preprocessor directive, all tokens to end of line are too. |
1324 | if (!Tok.isAtStartOfLine()) |
1325 | break; |
1326 | |
1327 | // Eat all of the tokens until we get to the next one at the start of |
1328 | // line. |
1329 | unsigned TokEnd = TokOffs + TokLen; |
1330 | L.LexFromRawLexer(Result&: Tok); |
1331 | while (!Tok.isAtStartOfLine() && Tok.isNot(K: tok::eof)) { |
1332 | TokEnd = SM.getFileOffset(SpellingLoc: Tok.getLocation()) + Tok.getLength(); |
1333 | L.LexFromRawLexer(Result&: Tok); |
1334 | } |
1335 | |
1336 | generator.addTag(name: "u" , attributes: {}, pos: TokOffs, len: TokEnd - TokOffs); |
1337 | |
1338 | // Don't skip the next token. |
1339 | continue; |
1340 | } |
1341 | } |
1342 | |
1343 | L.LexFromRawLexer(Result&: Tok); |
1344 | } |
1345 | } |
1346 | |
1347 | std::string Annotator::getParamNameForArg(clang::CallExpr *callExpr, clang::ParmVarDecl *paramDecl, |
1348 | clang::Expr *arg) |
1349 | { |
1350 | return InlayHintsAnnotatorHelper(this).getParamNameInlayHint(callExpr, paramDecl, arg); |
1351 | } |
1352 | |
1353 | llvm::DenseMap<clang::SourceLocation, std::string> |
1354 | Annotator::getDesignatorInlayHints(clang::InitListExpr *Syn) |
1355 | { |
1356 | InlayHintsAnnotatorHelper helper(this); |
1357 | return helper.getDesignatorInlayHints(Syn); |
1358 | } |
1359 | |