xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/SuspiciousIncludeCheck.cpp (revision da95d926f6fce4ed9707c77908ad96624268f134)
1 //===--- SuspiciousIncludeCheck.cpp - clang-tidy --------------------------===//
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 "SuspiciousIncludeCheck.h"
10 #include "../utils/FileExtensionsUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/Lex/Preprocessor.h"
13 #include <optional>
14 
15 namespace clang::tidy::bugprone {
16 
17 namespace {
18 class SuspiciousIncludePPCallbacks : public PPCallbacks {
19 public:
SuspiciousIncludePPCallbacks(SuspiciousIncludeCheck & Check,const SourceManager & SM,Preprocessor * PP)20   explicit SuspiciousIncludePPCallbacks(SuspiciousIncludeCheck &Check,
21                                         const SourceManager &SM,
22                                         Preprocessor *PP)
23       : Check(Check), PP(PP) {}
24 
25   void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
26                           StringRef FileName, bool IsAngled,
27                           CharSourceRange FilenameRange,
28                           OptionalFileEntryRef File, StringRef SearchPath,
29                           StringRef RelativePath, const Module *SuggestedModule,
30                           bool ModuleImported,
31                           SrcMgr::CharacteristicKind FileType) override;
32 
33 private:
34   SuspiciousIncludeCheck &Check;
35   Preprocessor *PP;
36 };
37 } // namespace
38 
SuspiciousIncludeCheck(StringRef Name,ClangTidyContext * Context)39 SuspiciousIncludeCheck::SuspiciousIncludeCheck(StringRef Name,
40                                                ClangTidyContext *Context)
41     : ClangTidyCheck(Name, Context),
42       HeaderFileExtensions(Context->getHeaderFileExtensions()),
43       ImplementationFileExtensions(Context->getImplementationFileExtensions()) {
44 }
45 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)46 void SuspiciousIncludeCheck::registerPPCallbacks(
47     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
48   PP->addPPCallbacks(
49       ::std::make_unique<SuspiciousIncludePPCallbacks>(*this, SM, PP));
50 }
51 
InclusionDirective(SourceLocation HashLoc,const Token & IncludeTok,StringRef FileName,bool IsAngled,CharSourceRange FilenameRange,OptionalFileEntryRef File,StringRef SearchPath,StringRef RelativePath,const Module * SuggestedModule,bool ModuleImported,SrcMgr::CharacteristicKind FileType)52 void SuspiciousIncludePPCallbacks::InclusionDirective(
53     SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName,
54     bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef File,
55     StringRef SearchPath, StringRef RelativePath, const Module *SuggestedModule,
56     bool ModuleImported, SrcMgr::CharacteristicKind FileType) {
57   if (IncludeTok.getIdentifierInfo()->getPPKeywordID() == tok::pp_import)
58     return;
59 
60   SourceLocation DiagLoc = FilenameRange.getBegin().getLocWithOffset(1);
61 
62   const std::optional<StringRef> IFE =
63       utils::getFileExtension(FileName, Check.ImplementationFileExtensions);
64   if (!IFE)
65     return;
66 
67   Check.diag(DiagLoc, "suspicious #%0 of file with '%1' extension")
68       << IncludeTok.getIdentifierInfo()->getName() << *IFE;
69 
70   for (const auto &HFE : Check.HeaderFileExtensions) {
71     SmallString<128> GuessedFileName(FileName);
72     llvm::sys::path::replace_extension(GuessedFileName,
73                                        (!HFE.empty() ? "." : "") + HFE);
74 
75     OptionalFileEntryRef File =
76         PP->LookupFile(DiagLoc, GuessedFileName, IsAngled, nullptr, nullptr,
77                        nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
78     if (File) {
79       Check.diag(DiagLoc, "did you mean to include '%0'?", DiagnosticIDs::Note)
80           << GuessedFileName;
81     }
82   }
83 }
84 
85 } // namespace clang::tidy::bugprone
86