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 | |