1 //===--- HeaderGuard.cpp - clang-tidy -------------------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "HeaderGuard.h" 11 #include "clang/Frontend/CompilerInstance.h" 12 #include "clang/Lex/PPCallbacks.h" 13 #include "clang/Lex/Preprocessor.h" 14 #include "clang/Tooling/Tooling.h" 15 #include "llvm/Support/Path.h" 16 17 namespace clang { 18 namespace tidy { 19 namespace utils { 20 21 /// \brief canonicalize a path by removing ./ and ../ components. 22 static std::string cleanPath(StringRef Path) { 23 SmallString<256> Result = Path; 24 llvm::sys::path::remove_dots(Result, true); 25 return Result.str(); 26 } 27 28 namespace { 29 class HeaderGuardPPCallbacks : public PPCallbacks { 30 public: 31 HeaderGuardPPCallbacks(Preprocessor *PP, HeaderGuardCheck *Check) 32 : PP(PP), Check(Check) {} 33 34 void FileChanged(SourceLocation Loc, FileChangeReason Reason, 35 SrcMgr::CharacteristicKind FileType, 36 FileID PrevFID) override { 37 // Record all files we enter. We'll need them to diagnose headers without 38 // guards. 39 SourceManager &SM = PP->getSourceManager(); 40 if (Reason == EnterFile && FileType == SrcMgr::C_User) { 41 if (const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Loc))) { 42 std::string FileName = cleanPath(FE->getName()); 43 Files[FileName] = FE; 44 } 45 } 46 } 47 48 void Ifndef(SourceLocation Loc, const Token &MacroNameTok, 49 const MacroDefinition &MD) override { 50 if (MD) 51 return; 52 53 // Record #ifndefs that succeeded. We also need the Location of the Name. 54 Ifndefs[MacroNameTok.getIdentifierInfo()] = 55 std::make_pair(Loc, MacroNameTok.getLocation()); 56 } 57 58 void MacroDefined(const Token &MacroNameTok, 59 const MacroDirective *MD) override { 60 // Record all defined macros. We store the whole token to get info on the 61 // name later. 62 Macros.emplace_back(MacroNameTok, MD->getMacroInfo()); 63 } 64 65 void Endif(SourceLocation Loc, SourceLocation IfLoc) override { 66 // Record all #endif and the corresponding #ifs (including #ifndefs). 67 EndIfs[IfLoc] = Loc; 68 } 69 70 void EndOfMainFile() override { 71 // Now that we have all this information from the preprocessor, use it! 72 SourceManager &SM = PP->getSourceManager(); 73 74 for (const auto &MacroEntry : Macros) { 75 const MacroInfo *MI = MacroEntry.second; 76 77 // We use clang's header guard detection. This has the advantage of also 78 // emitting a warning for cases where a pseudo header guard is found but 79 // preceded by something blocking the header guard optimization. 80 if (!MI->isUsedForHeaderGuard()) 81 continue; 82 83 const FileEntry *FE = 84 SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc())); 85 std::string FileName = cleanPath(FE->getName()); 86 Files.erase(FileName); 87 88 // See if we should check and fix this header guard. 89 if (!Check->shouldFixHeaderGuard(FileName)) 90 continue; 91 92 // Look up Locations for this guard. 93 SourceLocation Ifndef = 94 Ifndefs[MacroEntry.first.getIdentifierInfo()].second; 95 SourceLocation Define = MacroEntry.first.getLocation(); 96 SourceLocation EndIf = 97 EndIfs[Ifndefs[MacroEntry.first.getIdentifierInfo()].first]; 98 99 // If the macro Name is not equal to what we can compute, correct it in 100 // the #ifndef and #define. 101 StringRef CurHeaderGuard = 102 MacroEntry.first.getIdentifierInfo()->getName(); 103 std::vector<FixItHint> FixIts; 104 std::string NewGuard = checkHeaderGuardDefinition( 105 Ifndef, Define, EndIf, FileName, CurHeaderGuard, FixIts); 106 107 // Now look at the #endif. We want a comment with the header guard. Fix it 108 // at the slightest deviation. 109 checkEndifComment(FileName, EndIf, NewGuard, FixIts); 110 111 // Bundle all fix-its into one warning. The message depends on whether we 112 // changed the header guard or not. 113 if (!FixIts.empty()) { 114 if (CurHeaderGuard != NewGuard) { 115 Check->diag(Ifndef, "header guard does not follow preferred style") 116 << FixIts; 117 } else { 118 Check->diag(EndIf, "#endif for a header guard should reference the " 119 "guard macro in a comment") 120 << FixIts; 121 } 122 } 123 } 124 125 // Emit warnings for headers that are missing guards. 126 checkGuardlessHeaders(); 127 128 // Clear all state. 129 Macros.clear(); 130 Files.clear(); 131 Ifndefs.clear(); 132 EndIfs.clear(); 133 } 134 135 bool wouldFixEndifComment(StringRef FileName, SourceLocation EndIf, 136 StringRef HeaderGuard, 137 size_t *EndIfLenPtr = nullptr) { 138 if (!EndIf.isValid()) 139 return false; 140 const char *EndIfData = PP->getSourceManager().getCharacterData(EndIf); 141 size_t EndIfLen = std::strcspn(EndIfData, "\r\n"); 142 if (EndIfLenPtr) 143 *EndIfLenPtr = EndIfLen; 144 145 StringRef EndIfStr(EndIfData, EndIfLen); 146 EndIfStr = EndIfStr.substr(EndIfStr.find_first_not_of("#endif \t")); 147 148 // Give up if there's an escaped newline. 149 size_t FindEscapedNewline = EndIfStr.find_last_not_of(' '); 150 if (FindEscapedNewline != StringRef::npos && 151 EndIfStr[FindEscapedNewline] == '\\') 152 return false; 153 154 if (!Check->shouldSuggestEndifComment(FileName) && 155 !(EndIfStr.startswith("//") || 156 (EndIfStr.startswith("/*") && EndIfStr.endswith("*/")))) 157 return false; 158 159 return (EndIfStr != "// " + HeaderGuard.str()) && 160 (EndIfStr != "/* " + HeaderGuard.str() + " */"); 161 } 162 163 /// \brief Look for header guards that don't match the preferred style. Emit 164 /// fix-its and return the suggested header guard (or the original if no 165 /// change was made. 166 std::string checkHeaderGuardDefinition(SourceLocation Ifndef, 167 SourceLocation Define, 168 SourceLocation EndIf, 169 StringRef FileName, 170 StringRef CurHeaderGuard, 171 std::vector<FixItHint> &FixIts) { 172 std::string CPPVar = Check->getHeaderGuard(FileName, CurHeaderGuard); 173 std::string CPPVarUnder = CPPVar + '_'; 174 175 // Allow a trailing underscore iff we don't have to change the endif comment 176 // too. 177 if (Ifndef.isValid() && CurHeaderGuard != CPPVar && 178 (CurHeaderGuard != CPPVarUnder || 179 wouldFixEndifComment(FileName, EndIf, CurHeaderGuard))) { 180 FixIts.push_back(FixItHint::CreateReplacement( 181 CharSourceRange::getTokenRange( 182 Ifndef, Ifndef.getLocWithOffset(CurHeaderGuard.size())), 183 CPPVar)); 184 FixIts.push_back(FixItHint::CreateReplacement( 185 CharSourceRange::getTokenRange( 186 Define, Define.getLocWithOffset(CurHeaderGuard.size())), 187 CPPVar)); 188 return CPPVar; 189 } 190 return CurHeaderGuard; 191 } 192 193 /// \brief Checks the comment after the #endif of a header guard and fixes it 194 /// if it doesn't match \c HeaderGuard. 195 void checkEndifComment(StringRef FileName, SourceLocation EndIf, 196 StringRef HeaderGuard, 197 std::vector<FixItHint> &FixIts) { 198 size_t EndIfLen; 199 if (wouldFixEndifComment(FileName, EndIf, HeaderGuard, &EndIfLen)) { 200 FixIts.push_back(FixItHint::CreateReplacement( 201 CharSourceRange::getCharRange(EndIf, 202 EndIf.getLocWithOffset(EndIfLen)), 203 Check->formatEndIf(HeaderGuard))); 204 } 205 } 206 207 /// \brief Looks for files that were visited but didn't have a header guard. 208 /// Emits a warning with fixits suggesting adding one. 209 void checkGuardlessHeaders() { 210 // Look for header files that didn't have a header guard. Emit a warning and 211 // fix-its to add the guard. 212 // TODO: Insert the guard after top comments. 213 for (const auto &FE : Files) { 214 StringRef FileName = FE.getKey(); 215 if (!Check->shouldSuggestToAddHeaderGuard(FileName)) 216 continue; 217 218 SourceManager &SM = PP->getSourceManager(); 219 FileID FID = SM.translateFile(FE.getValue()); 220 SourceLocation StartLoc = SM.getLocForStartOfFile(FID); 221 if (StartLoc.isInvalid()) 222 continue; 223 224 std::string CPPVar = Check->getHeaderGuard(FileName); 225 std::string CPPVarUnder = CPPVar + '_'; // Allow a trailing underscore. 226 // If there's a macro with a name that follows the header guard convention 227 // but was not recognized by the preprocessor as a header guard there must 228 // be code outside of the guarded area. Emit a plain warning without 229 // fix-its. 230 // FIXME: Can we move it into the right spot? 231 bool SeenMacro = false; 232 for (const auto &MacroEntry : Macros) { 233 StringRef Name = MacroEntry.first.getIdentifierInfo()->getName(); 234 SourceLocation DefineLoc = MacroEntry.first.getLocation(); 235 if ((Name == CPPVar || Name == CPPVarUnder) && 236 SM.isWrittenInSameFile(StartLoc, DefineLoc)) { 237 Check->diag(DefineLoc, "code/includes outside of area guarded by " 238 "header guard; consider moving it"); 239 SeenMacro = true; 240 break; 241 } 242 } 243 244 if (SeenMacro) 245 continue; 246 247 Check->diag(StartLoc, "header is missing header guard") 248 << FixItHint::CreateInsertion( 249 StartLoc, "#ifndef " + CPPVar + "\n#define " + CPPVar + "\n\n") 250 << FixItHint::CreateInsertion( 251 SM.getLocForEndOfFile(FID), 252 Check->shouldSuggestEndifComment(FileName) 253 ? "\n#" + Check->formatEndIf(CPPVar) + "\n" 254 : "\n#endif\n"); 255 } 256 } 257 258 private: 259 std::vector<std::pair<Token, const MacroInfo *>> Macros; 260 llvm::StringMap<const FileEntry *> Files; 261 std::map<const IdentifierInfo *, std::pair<SourceLocation, SourceLocation>> 262 Ifndefs; 263 std::map<SourceLocation, SourceLocation> EndIfs; 264 265 Preprocessor *PP; 266 HeaderGuardCheck *Check; 267 }; 268 } // namespace 269 270 void HeaderGuardCheck::registerPPCallbacks(CompilerInstance &Compiler) { 271 Compiler.getPreprocessor().addPPCallbacks( 272 llvm::make_unique<HeaderGuardPPCallbacks>(&Compiler.getPreprocessor(), 273 this)); 274 } 275 276 bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName) { 277 return utils::isHeaderFileExtension(FileName, HeaderFileExtensions); 278 } 279 280 bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; } 281 282 bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName) { 283 return utils::isHeaderFileExtension(FileName, HeaderFileExtensions); 284 } 285 286 std::string HeaderGuardCheck::formatEndIf(StringRef HeaderGuard) { 287 return "endif // " + HeaderGuard.str(); 288 } 289 290 } // namespace utils 291 } // namespace tidy 292 } // namespace clang 293