| 1 | //===-- Mapper.cpp - ClangDoc Mapper ----------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "Mapper.h" |
| 10 | #include "Serialize.h" |
| 11 | #include "clang/AST/Comment.h" |
| 12 | #include "clang/Index/USRGeneration.h" |
| 13 | #include "llvm/ADT/StringExtras.h" |
| 14 | #include "llvm/ADT/StringSet.h" |
| 15 | #include "llvm/Support/Mutex.h" |
| 16 | #include "llvm/Support/TimeProfiler.h" |
| 17 | |
| 18 | namespace clang { |
| 19 | namespace doc { |
| 20 | |
| 21 | static llvm::StringSet<> USRVisited; |
| 22 | static llvm::sys::SmartMutex<true> USRVisitedGuard; |
| 23 | |
| 24 | template <typename T> static bool isTypedefAnonRecord(const T *D) { |
| 25 | if (const auto *C = dyn_cast<CXXRecordDecl>(D)) { |
| 26 | return C->getTypedefNameForAnonDecl(); |
| 27 | } |
| 28 | return false; |
| 29 | } |
| 30 | |
| 31 | Location MapASTVisitor::getDeclLocation(const NamedDecl *D) const { |
| 32 | bool IsFileInRootDir; |
| 33 | llvm::SmallString<128> File = |
| 34 | getFile(D, Context: D->getASTContext(), RootDir: CDCtx.SourceRoot, IsFileInRootDir); |
| 35 | SourceManager &SM = D->getASTContext().getSourceManager(); |
| 36 | int Start = SM.getPresumedLoc(Loc: D->getBeginLoc()).getLine(); |
| 37 | int End = SM.getPresumedLoc(Loc: D->getEndLoc()).getLine(); |
| 38 | |
| 39 | return Location(Start, End, File, IsFileInRootDir); |
| 40 | } |
| 41 | |
| 42 | void MapASTVisitor::HandleTranslationUnit(ASTContext &Context) { |
| 43 | if (CDCtx.FTimeTrace) |
| 44 | llvm::timeTraceProfilerInitialize(TimeTraceGranularity: 200, ProcName: "clang-doc" ); |
| 45 | TraverseDecl(Context.getTranslationUnitDecl()); |
| 46 | if (CDCtx.FTimeTrace) |
| 47 | llvm::timeTraceProfilerFinishThread(); |
| 48 | } |
| 49 | |
| 50 | template <typename T> |
| 51 | bool MapASTVisitor::mapDecl(const T *D, bool IsDefinition) { |
| 52 | llvm::TimeTraceScope TS("Mapping declaration" ); |
| 53 | { |
| 54 | llvm::TimeTraceScope TS("Preamble" ); |
| 55 | // If we're looking a decl not in user files, skip this decl. |
| 56 | if (D->getASTContext().getSourceManager().isInSystemHeader( |
| 57 | D->getLocation())) |
| 58 | return true; |
| 59 | |
| 60 | // Skip function-internal decls. |
| 61 | if (D->getParentFunctionOrMethod()) |
| 62 | return true; |
| 63 | } |
| 64 | |
| 65 | std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>> CP; |
| 66 | |
| 67 | { |
| 68 | llvm::TimeTraceScope TS("emit info from astnode" ); |
| 69 | llvm::SmallString<128> USR; |
| 70 | // If there is an error generating a USR for the decl, skip this decl. |
| 71 | if (index::generateUSRForDecl(D, USR)) |
| 72 | return true; |
| 73 | // Prevent Visiting USR twice |
| 74 | { |
| 75 | llvm::sys::SmartScopedLock<true> Guard(USRVisitedGuard); |
| 76 | StringRef Visited = USR.str(); |
| 77 | if (USRVisited.count(Key: Visited) && !isTypedefAnonRecord<T>(D)) |
| 78 | return true; |
| 79 | // We considered a USR to be visited only when its defined |
| 80 | if (IsDefinition) |
| 81 | USRVisited.insert(key: Visited); |
| 82 | } |
| 83 | bool IsFileInRootDir; |
| 84 | llvm::SmallString<128> File = |
| 85 | getFile(D, Context: D->getASTContext(), RootDir: CDCtx.SourceRoot, IsFileInRootDir); |
| 86 | CP = serialize::emitInfo(D, getComment(D, Context: D->getASTContext()), |
| 87 | getDeclLocation(D), CDCtx.PublicOnly); |
| 88 | } |
| 89 | |
| 90 | auto &[Child, Parent] = CP; |
| 91 | |
| 92 | { |
| 93 | llvm::TimeTraceScope TS("serialized info into bitcode" ); |
| 94 | // A null in place of a valid Info indicates that the serializer is skipping |
| 95 | // this decl for some reason (e.g. we're only reporting public decls). |
| 96 | if (Child) |
| 97 | CDCtx.ECtx->reportResult(Key: llvm::toHex(Input: llvm::toStringRef(Input: Child->USR)), |
| 98 | Value: serialize::serialize(I&: Child)); |
| 99 | if (Parent) |
| 100 | CDCtx.ECtx->reportResult(Key: llvm::toHex(Input: llvm::toStringRef(Input: Parent->USR)), |
| 101 | Value: serialize::serialize(I&: Parent)); |
| 102 | } |
| 103 | return true; |
| 104 | } |
| 105 | |
| 106 | bool MapASTVisitor::VisitNamespaceDecl(const NamespaceDecl *D) { |
| 107 | return mapDecl(D, /*isDefinition=*/IsDefinition: true); |
| 108 | } |
| 109 | |
| 110 | bool MapASTVisitor::VisitRecordDecl(const RecordDecl *D) { |
| 111 | return mapDecl(D, D->isThisDeclarationADefinition()); |
| 112 | } |
| 113 | |
| 114 | bool MapASTVisitor::VisitEnumDecl(const EnumDecl *D) { |
| 115 | return mapDecl(D, D->isThisDeclarationADefinition()); |
| 116 | } |
| 117 | |
| 118 | bool MapASTVisitor::VisitCXXMethodDecl(const CXXMethodDecl *D) { |
| 119 | return mapDecl(D, D->isThisDeclarationADefinition()); |
| 120 | } |
| 121 | |
| 122 | bool MapASTVisitor::VisitFunctionDecl(const FunctionDecl *D) { |
| 123 | // Don't visit CXXMethodDecls twice |
| 124 | if (isa<CXXMethodDecl>(Val: D)) |
| 125 | return true; |
| 126 | return mapDecl(D, IsDefinition: D->isThisDeclarationADefinition()); |
| 127 | } |
| 128 | |
| 129 | bool MapASTVisitor::VisitTypedefDecl(const TypedefDecl *D) { |
| 130 | return mapDecl(D, /*isDefinition=*/IsDefinition: true); |
| 131 | } |
| 132 | |
| 133 | bool MapASTVisitor::VisitTypeAliasDecl(const TypeAliasDecl *D) { |
| 134 | return mapDecl(D, /*isDefinition=*/IsDefinition: true); |
| 135 | } |
| 136 | |
| 137 | comments::FullComment * |
| 138 | MapASTVisitor::(const NamedDecl *D, const ASTContext &Context) const { |
| 139 | RawComment * = Context.getRawCommentForDeclNoCache(D); |
| 140 | // FIXME: Move setAttached to the initial comment parsing. |
| 141 | if (Comment) { |
| 142 | Comment->setAttached(); |
| 143 | return Comment->parse(Context, nullptr, D); |
| 144 | } |
| 145 | return nullptr; |
| 146 | } |
| 147 | |
| 148 | int MapASTVisitor::getLine(const NamedDecl *D, |
| 149 | const ASTContext &Context) const { |
| 150 | return Context.getSourceManager().getPresumedLoc(Loc: D->getBeginLoc()).getLine(); |
| 151 | } |
| 152 | |
| 153 | llvm::SmallString<128> MapASTVisitor::getFile(const NamedDecl *D, |
| 154 | const ASTContext &Context, |
| 155 | llvm::StringRef RootDir, |
| 156 | bool &IsFileInRootDir) const { |
| 157 | llvm::SmallString<128> File(Context.getSourceManager() |
| 158 | .getPresumedLoc(Loc: D->getBeginLoc()) |
| 159 | .getFilename()); |
| 160 | IsFileInRootDir = false; |
| 161 | if (RootDir.empty() || !File.starts_with(Prefix: RootDir)) |
| 162 | return File; |
| 163 | IsFileInRootDir = true; |
| 164 | llvm::SmallString<128> Prefix(RootDir); |
| 165 | // replace_path_prefix removes the exact prefix provided. The result of |
| 166 | // calling that function on ("A/B/C.c", "A/B", "") would be "/C.c", which |
| 167 | // starts with a / that is not needed. This is why we fix Prefix so it always |
| 168 | // ends with a / and the result has the desired format. |
| 169 | if (!llvm::sys::path::is_separator(value: Prefix.back())) |
| 170 | Prefix += llvm::sys::path::get_separator(); |
| 171 | llvm::sys::path::replace_path_prefix(Path&: File, OldPrefix: Prefix, NewPrefix: "" ); |
| 172 | return File; |
| 173 | } |
| 174 | |
| 175 | } // namespace doc |
| 176 | } // namespace clang |
| 177 | |