| 1 | //===--- Record.cpp - Record compiler events ------------------------------===// |
| 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 "clang-include-cleaner/Record.h" |
| 10 | #include "clang-include-cleaner/Types.h" |
| 11 | #include "clang/AST/ASTConsumer.h" |
| 12 | #include "clang/AST/ASTContext.h" |
| 13 | #include "clang/AST/DeclGroup.h" |
| 14 | #include "clang/Basic/FileEntry.h" |
| 15 | #include "clang/Basic/FileManager.h" |
| 16 | #include "clang/Basic/LLVM.h" |
| 17 | #include "clang/Basic/SourceLocation.h" |
| 18 | #include "clang/Basic/SourceManager.h" |
| 19 | #include "clang/Basic/Specifiers.h" |
| 20 | #include "clang/Frontend/CompilerInstance.h" |
| 21 | #include "clang/Lex/DirectoryLookup.h" |
| 22 | #include "clang/Lex/MacroInfo.h" |
| 23 | #include "clang/Lex/PPCallbacks.h" |
| 24 | #include "clang/Lex/Preprocessor.h" |
| 25 | #include "clang/Tooling/Inclusions/HeaderAnalysis.h" |
| 26 | #include "clang/Tooling/Inclusions/StandardLibrary.h" |
| 27 | #include "llvm/ADT/ArrayRef.h" |
| 28 | #include "llvm/ADT/DenseMap.h" |
| 29 | #include "llvm/ADT/STLExtras.h" |
| 30 | #include "llvm/ADT/SmallSet.h" |
| 31 | #include "llvm/ADT/SmallVector.h" |
| 32 | #include "llvm/ADT/StringRef.h" |
| 33 | #include "llvm/ADT/iterator_range.h" |
| 34 | #include "llvm/Support/Allocator.h" |
| 35 | #include "llvm/Support/Error.h" |
| 36 | #include "llvm/Support/FileSystem/UniqueID.h" |
| 37 | #include "llvm/Support/Path.h" |
| 38 | #include "llvm/Support/StringSaver.h" |
| 39 | #include <algorithm> |
| 40 | #include <assert.h> |
| 41 | #include <memory> |
| 42 | #include <optional> |
| 43 | #include <set> |
| 44 | #include <utility> |
| 45 | #include <vector> |
| 46 | |
| 47 | namespace clang::include_cleaner { |
| 48 | namespace { |
| 49 | |
| 50 | class PPRecorder : public PPCallbacks { |
| 51 | public: |
| 52 | PPRecorder(RecordedPP &Recorded, const Preprocessor &PP) |
| 53 | : Recorded(Recorded), PP(PP), SM(PP.getSourceManager()) { |
| 54 | for (const auto &Dir : PP.getHeaderSearchInfo().search_dir_range()) |
| 55 | if (Dir.getLookupType() == DirectoryLookup::LT_NormalDir) |
| 56 | Recorded.Includes.addSearchDirectory(Dir.getDirRef()->getName()); |
| 57 | } |
| 58 | |
| 59 | void FileChanged(SourceLocation Loc, FileChangeReason Reason, |
| 60 | SrcMgr::CharacteristicKind FileType, |
| 61 | FileID PrevFID) override { |
| 62 | Active = SM.isWrittenInMainFile(Loc); |
| 63 | } |
| 64 | |
| 65 | void InclusionDirective(SourceLocation Hash, const Token &IncludeTok, |
| 66 | StringRef SpelledFilename, bool IsAngled, |
| 67 | CharSourceRange FilenameRange, |
| 68 | OptionalFileEntryRef File, StringRef SearchPath, |
| 69 | StringRef RelativePath, const Module *SuggestedModule, |
| 70 | bool ModuleImported, |
| 71 | SrcMgr::CharacteristicKind) override { |
| 72 | if (!Active) |
| 73 | return; |
| 74 | |
| 75 | Include I; |
| 76 | I.HashLocation = Hash; |
| 77 | I.Resolved = File; |
| 78 | I.Line = SM.getSpellingLineNumber(Loc: Hash); |
| 79 | I.Spelled = SpelledFilename; |
| 80 | I.Angled = IsAngled; |
| 81 | Recorded.Includes.add(I); |
| 82 | } |
| 83 | |
| 84 | void MacroExpands(const Token &MacroName, const MacroDefinition &MD, |
| 85 | SourceRange Range, const MacroArgs *Args) override { |
| 86 | if (!Active) |
| 87 | return; |
| 88 | recordMacroRef(Tok: MacroName, MI: *MD.getMacroInfo()); |
| 89 | } |
| 90 | |
| 91 | void MacroDefined(const Token &MacroName, const MacroDirective *MD) override { |
| 92 | if (!Active) |
| 93 | return; |
| 94 | |
| 95 | const auto *MI = MD->getMacroInfo(); |
| 96 | // The tokens of a macro definition could refer to a macro. |
| 97 | // Formally this reference isn't resolved until this macro is expanded, |
| 98 | // but we want to treat it as a reference anyway. |
| 99 | for (const auto &Tok : MI->tokens()) { |
| 100 | auto *II = Tok.getIdentifierInfo(); |
| 101 | // Could this token be a reference to a macro? (Not param to this macro). |
| 102 | if (!II || !II->hadMacroDefinition() || |
| 103 | llvm::is_contained(Range: MI->params(), Element: II)) |
| 104 | continue; |
| 105 | if (const MacroInfo *MI = PP.getMacroInfo(II)) |
| 106 | recordMacroRef(Tok, MI: *MI); |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | void MacroUndefined(const Token &MacroName, const MacroDefinition &MD, |
| 111 | const MacroDirective *) override { |
| 112 | if (!Active) |
| 113 | return; |
| 114 | if (const auto *MI = MD.getMacroInfo()) |
| 115 | recordMacroRef(Tok: MacroName, MI: *MI); |
| 116 | } |
| 117 | |
| 118 | void Ifdef(SourceLocation Loc, const Token &MacroNameTok, |
| 119 | const MacroDefinition &MD) override { |
| 120 | if (!Active) |
| 121 | return; |
| 122 | if (const auto *MI = MD.getMacroInfo()) |
| 123 | recordMacroRef(Tok: MacroNameTok, MI: *MI, RT: RefType::Ambiguous); |
| 124 | } |
| 125 | |
| 126 | void Ifndef(SourceLocation Loc, const Token &MacroNameTok, |
| 127 | const MacroDefinition &MD) override { |
| 128 | if (!Active) |
| 129 | return; |
| 130 | if (const auto *MI = MD.getMacroInfo()) |
| 131 | recordMacroRef(Tok: MacroNameTok, MI: *MI, RT: RefType::Ambiguous); |
| 132 | } |
| 133 | |
| 134 | using PPCallbacks::Elifdef; |
| 135 | using PPCallbacks::Elifndef; |
| 136 | void Elifdef(SourceLocation Loc, const Token &MacroNameTok, |
| 137 | const MacroDefinition &MD) override { |
| 138 | if (!Active) |
| 139 | return; |
| 140 | if (const auto *MI = MD.getMacroInfo()) |
| 141 | recordMacroRef(Tok: MacroNameTok, MI: *MI, RT: RefType::Ambiguous); |
| 142 | } |
| 143 | void Elifndef(SourceLocation Loc, const Token &MacroNameTok, |
| 144 | const MacroDefinition &MD) override { |
| 145 | if (!Active) |
| 146 | return; |
| 147 | if (const auto *MI = MD.getMacroInfo()) |
| 148 | recordMacroRef(Tok: MacroNameTok, MI: *MI, RT: RefType::Ambiguous); |
| 149 | } |
| 150 | |
| 151 | void Defined(const Token &MacroNameTok, const MacroDefinition &MD, |
| 152 | SourceRange Range) override { |
| 153 | if (!Active) |
| 154 | return; |
| 155 | if (const auto *MI = MD.getMacroInfo()) |
| 156 | recordMacroRef(Tok: MacroNameTok, MI: *MI, RT: RefType::Ambiguous); |
| 157 | } |
| 158 | |
| 159 | private: |
| 160 | void recordMacroRef(const Token &Tok, const MacroInfo &MI, |
| 161 | RefType RT = RefType::Explicit) { |
| 162 | if (MI.isBuiltinMacro()) |
| 163 | return; // __FILE__ is not a reference. |
| 164 | Recorded.MacroReferences.push_back( |
| 165 | x: SymbolReference{.Target: Macro{.Name: Tok.getIdentifierInfo(), .Definition: MI.getDefinitionLoc()}, |
| 166 | .RefLocation: Tok.getLocation(), .RT: RT}); |
| 167 | } |
| 168 | |
| 169 | bool Active = false; |
| 170 | RecordedPP &Recorded; |
| 171 | const Preprocessor &PP; |
| 172 | const SourceManager &SM; |
| 173 | }; |
| 174 | |
| 175 | } // namespace |
| 176 | |
| 177 | class PragmaIncludes::RecordPragma : public PPCallbacks, public CommentHandler { |
| 178 | public: |
| 179 | RecordPragma(const CompilerInstance &CI, PragmaIncludes *Out) |
| 180 | : RecordPragma(CI.getPreprocessor(), Out) {} |
| 181 | RecordPragma(const Preprocessor &P, PragmaIncludes *Out) |
| 182 | : SM(P.getSourceManager()), HeaderInfo(P.getHeaderSearchInfo()), Out(Out), |
| 183 | Arena(std::make_shared<llvm::BumpPtrAllocator>()), |
| 184 | UniqueStrings(*Arena), |
| 185 | MainFileStem(llvm::sys::path::stem( |
| 186 | path: SM.getNonBuiltinFilenameForID(FID: SM.getMainFileID()).value_or(u: "" ))) {} |
| 187 | |
| 188 | void FileChanged(SourceLocation Loc, FileChangeReason Reason, |
| 189 | SrcMgr::CharacteristicKind FileType, |
| 190 | FileID PrevFID) override { |
| 191 | InMainFile = SM.isWrittenInMainFile(Loc); |
| 192 | |
| 193 | if (Reason == PPCallbacks::ExitFile) { |
| 194 | // At file exit time HeaderSearchInfo is valid and can be used to |
| 195 | // determine whether the file was a self-contained header or not. |
| 196 | if (OptionalFileEntryRef FE = SM.getFileEntryRefForID(FID: PrevFID)) { |
| 197 | if (tooling::isSelfContainedHeader(FE: *FE, SM, HeaderInfo)) |
| 198 | Out->NonSelfContainedFiles.erase(V: FE->getUniqueID()); |
| 199 | else |
| 200 | Out->NonSelfContainedFiles.insert(V: FE->getUniqueID()); |
| 201 | } |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | void EndOfMainFile() override { |
| 206 | for (auto &It : Out->IWYUExportBy) { |
| 207 | llvm::sort(C&: It.getSecond()); |
| 208 | It.getSecond().erase(CS: llvm::unique(R&: It.getSecond()), CE: It.getSecond().end()); |
| 209 | } |
| 210 | Out->Arena.emplace_back(args: std::move(Arena)); |
| 211 | } |
| 212 | |
| 213 | void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, |
| 214 | llvm::StringRef FileName, bool IsAngled, |
| 215 | CharSourceRange /*FilenameRange*/, |
| 216 | OptionalFileEntryRef File, |
| 217 | llvm::StringRef /*SearchPath*/, |
| 218 | llvm::StringRef /*RelativePath*/, |
| 219 | const clang::Module * /*SuggestedModule*/, |
| 220 | bool /*ModuleImported*/, |
| 221 | SrcMgr::CharacteristicKind FileKind) override { |
| 222 | FileID HashFID = SM.getFileID(SpellingLoc: HashLoc); |
| 223 | int HashLine = SM.getLineNumber(FID: HashFID, FilePos: SM.getFileOffset(SpellingLoc: HashLoc)); |
| 224 | std::optional<Header> ; |
| 225 | if (IsAngled) |
| 226 | if (auto StandardHeader = |
| 227 | tooling::stdlib::Header::named(Name: "<" + FileName.str() + ">" )) { |
| 228 | IncludedHeader = *StandardHeader; |
| 229 | } |
| 230 | if (!IncludedHeader && File) |
| 231 | IncludedHeader = *File; |
| 232 | checkForExport(IncludingFile: HashFID, HashLine, IncludedHeader, IncludedFile: File); |
| 233 | checkForKeep(HashLine, IncludedFile: File); |
| 234 | checkForDeducedAssociated(H: IncludedHeader); |
| 235 | } |
| 236 | |
| 237 | void checkForExport(FileID IncludingFile, int HashLine, |
| 238 | std::optional<Header> , |
| 239 | OptionalFileEntryRef IncludedFile) { |
| 240 | if (ExportStack.empty()) |
| 241 | return; |
| 242 | auto &Top = ExportStack.back(); |
| 243 | if (Top.SeenAtFile != IncludingFile) |
| 244 | return; |
| 245 | // Make sure current include is covered by the export pragma. |
| 246 | if ((Top.Block && HashLine > Top.SeenAtLine) || |
| 247 | Top.SeenAtLine == HashLine) { |
| 248 | if (IncludedFile) |
| 249 | Out->IWYUExportBy[IncludedFile->getUniqueID()].push_back(Elt: Top.Path); |
| 250 | if (IncludedHeader && IncludedHeader->kind() == Header::Standard) |
| 251 | Out->StdIWYUExportBy[IncludedHeader->standard()].push_back(Elt: Top.Path); |
| 252 | // main-file #include with export pragma should never be removed. |
| 253 | if (Top.SeenAtFile == SM.getMainFileID() && IncludedFile) |
| 254 | Out->ShouldKeep.insert(V: IncludedFile->getUniqueID()); |
| 255 | } |
| 256 | if (!Top.Block) // Pop immediately for single-line export pragma. |
| 257 | ExportStack.pop_back(); |
| 258 | } |
| 259 | |
| 260 | void checkForKeep(int HashLine, OptionalFileEntryRef IncludedFile) { |
| 261 | if (!InMainFile || KeepStack.empty()) |
| 262 | return; |
| 263 | KeepPragma &Top = KeepStack.back(); |
| 264 | // Check if the current include is covered by a keep pragma. |
| 265 | if (IncludedFile && ((Top.Block && HashLine > Top.SeenAtLine) || |
| 266 | Top.SeenAtLine == HashLine)) { |
| 267 | Out->ShouldKeep.insert(V: IncludedFile->getUniqueID()); |
| 268 | } |
| 269 | |
| 270 | if (!Top.Block) |
| 271 | KeepStack.pop_back(); // Pop immediately for single-line keep pragma. |
| 272 | } |
| 273 | |
| 274 | // Consider marking H as the "associated header" of the main file. |
| 275 | // |
| 276 | // Our heuristic: |
| 277 | // - it must be the first #include in the main file |
| 278 | // - it must have the same name stem as the main file (foo.h and foo.cpp) |
| 279 | // (IWYU pragma: associated is also supported, just not by this function). |
| 280 | // |
| 281 | // We consider the associated header as if it had a keep pragma. |
| 282 | // (Unlike IWYU, we don't treat #includes inside the associated header as if |
| 283 | // they were written in the main file.) |
| 284 | void checkForDeducedAssociated(std::optional<Header> H) { |
| 285 | namespace path = llvm::sys::path; |
| 286 | if (!InMainFile || SeenAssociatedCandidate) |
| 287 | return; |
| 288 | SeenAssociatedCandidate = true; // Only the first #include is our candidate. |
| 289 | if (!H || H->kind() != Header::Physical) |
| 290 | return; |
| 291 | if (path::stem(path: H->physical().getName(), style: path::Style::posix) == MainFileStem) |
| 292 | Out->ShouldKeep.insert(V: H->physical().getUniqueID()); |
| 293 | } |
| 294 | |
| 295 | bool HandleComment(Preprocessor &PP, SourceRange Range) override { |
| 296 | auto &SM = PP.getSourceManager(); |
| 297 | auto Pragma = |
| 298 | tooling::parseIWYUPragma(Text: SM.getCharacterData(SL: Range.getBegin())); |
| 299 | if (!Pragma) |
| 300 | return false; |
| 301 | |
| 302 | auto [CommentFID, CommentOffset] = SM.getDecomposedLoc(Loc: Range.getBegin()); |
| 303 | int = SM.getLineNumber(FID: CommentFID, FilePos: CommentOffset); |
| 304 | |
| 305 | if (InMainFile) { |
| 306 | if (Pragma->starts_with(Prefix: "keep" ) || |
| 307 | // Limited support for associated headers: never consider unused. |
| 308 | Pragma->starts_with(Prefix: "associated" )) { |
| 309 | KeepStack.push_back(x: {.SeenAtLine: CommentLine, .Block: false}); |
| 310 | } else if (Pragma->starts_with(Prefix: "begin_keep" )) { |
| 311 | KeepStack.push_back(x: {.SeenAtLine: CommentLine, .Block: true}); |
| 312 | } else if (Pragma->starts_with(Prefix: "end_keep" ) && !KeepStack.empty()) { |
| 313 | assert(KeepStack.back().Block); |
| 314 | KeepStack.pop_back(); |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | auto FE = SM.getFileEntryRefForID(FID: CommentFID); |
| 319 | if (!FE) { |
| 320 | // This can only happen when the buffer was registered virtually into |
| 321 | // SourceManager and FileManager has no idea about it. In such a scenario, |
| 322 | // that file cannot be discovered by HeaderSearch, therefore no "explicit" |
| 323 | // includes for that file. |
| 324 | return false; |
| 325 | } |
| 326 | auto = FE->getUniqueID(); |
| 327 | if (Pragma->consume_front(Prefix: "private" )) { |
| 328 | StringRef ; |
| 329 | if (Pragma->consume_front(Prefix: ", include " )) { |
| 330 | // We always insert using the spelling from the pragma. |
| 331 | PublicHeader = |
| 332 | save(S: Pragma->starts_with(Prefix: "<" ) || Pragma->starts_with(Prefix: "\"" ) |
| 333 | ? (*Pragma) |
| 334 | : ("\"" + *Pragma + "\"" ).str()); |
| 335 | } |
| 336 | Out->IWYUPublic.insert(KV: {CommentUID, PublicHeader}); |
| 337 | return false; |
| 338 | } |
| 339 | if (Pragma->consume_front(Prefix: "always_keep" )) { |
| 340 | Out->ShouldKeep.insert(V: CommentUID); |
| 341 | return false; |
| 342 | } |
| 343 | auto Filename = FE->getName(); |
| 344 | // Record export pragma. |
| 345 | if (Pragma->starts_with(Prefix: "export" )) { |
| 346 | ExportStack.push_back(x: {.SeenAtLine: CommentLine, .SeenAtFile: CommentFID, .Path: save(S: Filename), .Block: false}); |
| 347 | } else if (Pragma->starts_with(Prefix: "begin_exports" )) { |
| 348 | ExportStack.push_back(x: {.SeenAtLine: CommentLine, .SeenAtFile: CommentFID, .Path: save(S: Filename), .Block: true}); |
| 349 | } else if (Pragma->starts_with(Prefix: "end_exports" )) { |
| 350 | // FIXME: be robust on unmatching cases. We should only pop the stack if |
| 351 | // the begin_exports and end_exports is in the same file. |
| 352 | if (!ExportStack.empty()) { |
| 353 | assert(ExportStack.back().Block); |
| 354 | ExportStack.pop_back(); |
| 355 | } |
| 356 | } |
| 357 | return false; |
| 358 | } |
| 359 | |
| 360 | private: |
| 361 | StringRef save(llvm::StringRef S) { return UniqueStrings.save(S); } |
| 362 | |
| 363 | bool InMainFile = false; |
| 364 | const SourceManager &SM; |
| 365 | const HeaderSearch &HeaderInfo; |
| 366 | PragmaIncludes *Out; |
| 367 | std::shared_ptr<llvm::BumpPtrAllocator> Arena; |
| 368 | /// Intern table for strings. Contents are on the arena. |
| 369 | llvm::StringSaver UniqueStrings; |
| 370 | // Used when deducing associated header. |
| 371 | llvm::StringRef MainFileStem; |
| 372 | bool SeenAssociatedCandidate = false; |
| 373 | |
| 374 | struct ExportPragma { |
| 375 | // The line number where we saw the begin_exports or export pragma. |
| 376 | int SeenAtLine = 0; // 1-based line number. |
| 377 | // The file where we saw the pragma. |
| 378 | FileID SeenAtFile; |
| 379 | // Name (per FileEntry::getName()) of the file SeenAtFile. |
| 380 | StringRef Path; |
| 381 | // true if it is a block begin/end_exports pragma; false if it is a |
| 382 | // single-line export pragma. |
| 383 | bool Block = false; |
| 384 | }; |
| 385 | // A stack for tracking all open begin_exports or single-line export. |
| 386 | std::vector<ExportPragma> ExportStack; |
| 387 | |
| 388 | struct KeepPragma { |
| 389 | // The line number where we saw the begin_keep or keep pragma. |
| 390 | int SeenAtLine = 0; // 1-based line number. |
| 391 | // true if it is a block begin/end_keep pragma; false if it is a |
| 392 | // single-line keep pragma. |
| 393 | bool Block = false; |
| 394 | }; |
| 395 | // A stack for tracking all open begin_keep pragmas or single-line keeps. |
| 396 | std::vector<KeepPragma> KeepStack; |
| 397 | }; |
| 398 | |
| 399 | void PragmaIncludes::record(const CompilerInstance &CI) { |
| 400 | auto Record = std::make_unique<RecordPragma>(args: CI, args: this); |
| 401 | CI.getPreprocessor().addCommentHandler(Handler: Record.get()); |
| 402 | CI.getPreprocessor().addPPCallbacks(C: std::move(Record)); |
| 403 | } |
| 404 | |
| 405 | void PragmaIncludes::record(Preprocessor &P) { |
| 406 | auto Record = std::make_unique<RecordPragma>(args&: P, args: this); |
| 407 | P.addCommentHandler(Handler: Record.get()); |
| 408 | P.addPPCallbacks(C: std::move(Record)); |
| 409 | } |
| 410 | |
| 411 | llvm::StringRef PragmaIncludes::getPublic(const FileEntry *F) const { |
| 412 | auto It = IWYUPublic.find(Val: F->getUniqueID()); |
| 413 | if (It == IWYUPublic.end()) |
| 414 | return "" ; |
| 415 | return It->getSecond(); |
| 416 | } |
| 417 | |
| 418 | static llvm::SmallVector<FileEntryRef> |
| 419 | toFileEntries(llvm::ArrayRef<StringRef> FileNames, FileManager &FM) { |
| 420 | llvm::SmallVector<FileEntryRef> Results; |
| 421 | |
| 422 | for (auto FName : FileNames) { |
| 423 | // FIMXE: log the failing cases? |
| 424 | if (auto FE = FM.getOptionalFileRef(Filename: FName)) |
| 425 | Results.push_back(Elt: *FE); |
| 426 | } |
| 427 | return Results; |
| 428 | } |
| 429 | llvm::SmallVector<FileEntryRef> |
| 430 | PragmaIncludes::getExporters(const FileEntry *File, FileManager &FM) const { |
| 431 | auto It = IWYUExportBy.find(Val: File->getUniqueID()); |
| 432 | if (It == IWYUExportBy.end()) |
| 433 | return {}; |
| 434 | |
| 435 | return toFileEntries(FileNames: It->getSecond(), FM); |
| 436 | } |
| 437 | llvm::SmallVector<FileEntryRef> |
| 438 | PragmaIncludes::getExporters(tooling::stdlib::Header , |
| 439 | FileManager &FM) const { |
| 440 | auto It = StdIWYUExportBy.find(Val: StdHeader); |
| 441 | if (It == StdIWYUExportBy.end()) |
| 442 | return {}; |
| 443 | return toFileEntries(FileNames: It->getSecond(), FM); |
| 444 | } |
| 445 | |
| 446 | bool PragmaIncludes::isSelfContained(const FileEntry *FE) const { |
| 447 | return !NonSelfContainedFiles.contains(V: FE->getUniqueID()); |
| 448 | } |
| 449 | |
| 450 | bool PragmaIncludes::isPrivate(const FileEntry *FE) const { |
| 451 | return IWYUPublic.contains(Val: FE->getUniqueID()); |
| 452 | } |
| 453 | |
| 454 | bool PragmaIncludes::shouldKeep(const FileEntry *FE) const { |
| 455 | return ShouldKeep.contains(V: FE->getUniqueID()) || |
| 456 | NonSelfContainedFiles.contains(V: FE->getUniqueID()); |
| 457 | } |
| 458 | |
| 459 | namespace { |
| 460 | template <typename T> bool isImplicitTemplateSpecialization(const Decl *D) { |
| 461 | if (const auto *TD = dyn_cast<T>(D)) |
| 462 | return TD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation; |
| 463 | return false; |
| 464 | } |
| 465 | } // namespace |
| 466 | |
| 467 | std::unique_ptr<ASTConsumer> RecordedAST::record() { |
| 468 | class Recorder : public ASTConsumer { |
| 469 | RecordedAST *Out; |
| 470 | |
| 471 | public: |
| 472 | Recorder(RecordedAST *Out) : Out(Out) {} |
| 473 | void Initialize(ASTContext &Ctx) override { Out->Ctx = &Ctx; } |
| 474 | bool HandleTopLevelDecl(DeclGroupRef DG) override { |
| 475 | const auto &SM = Out->Ctx->getSourceManager(); |
| 476 | for (Decl *D : DG) { |
| 477 | if (!SM.isWrittenInMainFile(Loc: SM.getExpansionLoc(Loc: D->getLocation()))) |
| 478 | continue; |
| 479 | if (isImplicitTemplateSpecialization<FunctionDecl>(D) || |
| 480 | isImplicitTemplateSpecialization<CXXRecordDecl>(D) || |
| 481 | isImplicitTemplateSpecialization<VarDecl>(D)) |
| 482 | continue; |
| 483 | // FIXME: Filter out certain Obj-C as well. |
| 484 | Out->Roots.push_back(x: D); |
| 485 | } |
| 486 | return ASTConsumer::HandleTopLevelDecl(D: DG); |
| 487 | } |
| 488 | }; |
| 489 | |
| 490 | return std::make_unique<Recorder>(args: this); |
| 491 | } |
| 492 | |
| 493 | std::unique_ptr<PPCallbacks> RecordedPP::record(const Preprocessor &PP) { |
| 494 | return std::make_unique<PPRecorder>(args&: *this, args: PP); |
| 495 | } |
| 496 | |
| 497 | } // namespace clang::include_cleaner |
| 498 | |