brintos

brintos / llvm-project-archived public Read only

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