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