|
|
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
/* This code was originally taken from part of the Clang-Tidy LLVM project and
* modified for use with CMake under the following original license: */
//===--- HeaderGuard.cpp - clang-tidy
//-------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM
// Exceptions. See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "UsePragmaOnceCheck.h"
#include <algorithm>
#include <cassert>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Lex/PPCallbacks.h>
#include <clang/Lex/Preprocessor.h>
#include <clang/Tooling/Tooling.h>
#include <llvm/Support/Path.h>
namespace clang { namespace tidy { namespace cmake {
/// canonicalize a path by removing ./ and ../ components.
static std::string cleanPath(StringRef Path) { SmallString<256> Result = Path; llvm::sys::path::remove_dots(Result, true); return std::string(Result.str()); }
namespace { // This class is a workaround for the fact that PPCallbacks doesn't give us the
// location of the hash for an #ifndef, #define, or #endif, so we have to find
// it ourselves. We can't lex backwards, and attempting to turn on the
// preprocessor's backtracking functionality wreaks havoc, so we have to
// instantiate a second lexer and lex all the way from the beginning of the
// file. Cache the results of this lexing so that we don't have to do it more
// times than needed.
//
// TODO: Upstream a change to LLVM to give us the location of the hash in
// PPCallbacks so we don't have to do this workaround.
class DirectiveCache { public: DirectiveCache(Preprocessor* PP, FileID FID) : PP(PP) , FID(FID) { SourceManager& SM = this->PP->getSourceManager(); OptionalFileEntryRef Entry = SM.getFileEntryRefForID(FID); assert(Entry && "Invalid FileID given");
Lexer MyLexer(FID, SM.getMemoryBufferForFileOrFake(*Entry), SM, this->PP->getLangOpts()); Token Tok;
while (!MyLexer.LexFromRawLexer(Tok)) { if (Tok.getKind() == tok::hash) { assert(SM.getFileID(Tok.getLocation()) == this->FID && "Token FileID does not match passed FileID"); if (!this->HashLocs.empty()) { assert(SM.getFileOffset(this->HashLocs.back()) < SM.getFileOffset(Tok.getLocation()) && "Tokens in file are not in order"); }
this->HashLocs.push_back(Tok.getLocation()); } } }
SourceRange createRangeForIfndef(SourceLocation IfndefMacroTokLoc) { // The #ifndef of an include guard is likely near the beginning of the
// file, so search from the front.
return SourceRange(this->findPreviousHashFromFront(IfndefMacroTokLoc), IfndefMacroTokLoc); }
SourceRange createRangeForDefine(SourceLocation DefineMacroTokLoc) { // The #define of an include guard is likely near the beginning of the
// file, so search from the front.
return SourceRange(this->findPreviousHashFromFront(DefineMacroTokLoc), DefineMacroTokLoc); }
SourceRange createRangeForEndif(SourceLocation EndifLoc) { // The #endif of an include guard is likely near the end of the file, so
// search from the back.
return SourceRange(this->findPreviousHashFromBack(EndifLoc), EndifLoc); }
private: Preprocessor* PP; FileID FID; SmallVector<SourceLocation> HashLocs;
SourceLocation findPreviousHashFromFront(SourceLocation Loc) { SourceManager& SM = this->PP->getSourceManager(); Loc = SM.getExpansionLoc(Loc); assert(SM.getFileID(Loc) == this->FID && "Loc FileID does not match our FileID");
auto It = std::find_if( this->HashLocs.begin(), this->HashLocs.end(), [&SM, &Loc](SourceLocation const& OtherLoc) -> bool { return SM.getFileOffset(OtherLoc) >= SM.getFileOffset(Loc); }); assert(It != this->HashLocs.begin() && "No hash associated with passed Loc"); return *--It; }
SourceLocation findPreviousHashFromBack(SourceLocation Loc) { SourceManager& SM = this->PP->getSourceManager(); Loc = SM.getExpansionLoc(Loc); assert(SM.getFileID(Loc) == this->FID && "Loc FileID does not match our FileID");
auto It = std::find_if(this->HashLocs.rbegin(), this->HashLocs.rend(), [&SM, &Loc](SourceLocation const& OtherLoc) -> bool { return SM.getFileOffset(OtherLoc) < SM.getFileOffset(Loc); }); assert(It != this->HashLocs.rend() && "No hash associated with passed Loc"); return *It; } };
class UsePragmaOncePPCallbacks : public PPCallbacks { public: UsePragmaOncePPCallbacks(Preprocessor* PP, UsePragmaOnceCheck* Check) : PP(PP) , Check(Check) { }
void FileChanged(SourceLocation Loc, FileChangeReason Reason, SrcMgr::CharacteristicKind FileType, FileID PrevFID) override { // Record all files we enter. We'll need them to diagnose headers without
// guards.
SourceManager& SM = this->PP->getSourceManager(); if (Reason == EnterFile && FileType == SrcMgr::C_User) { if (OptionalFileEntryRef FE = SM.getFileEntryRefForID(SM.getFileID(Loc))) { std::string FileName = cleanPath(FE->getName()); this->Files.try_emplace(FileName, *FE); } } }
void Ifndef(SourceLocation Loc, Token const& MacroNameTok, MacroDefinition const& MD) override { if (MD) { return; }
// Record #ifndefs that succeeded. We also need the Location of the Name.
this->Ifndefs[MacroNameTok.getIdentifierInfo()] = std::make_pair(Loc, MacroNameTok.getLocation()); }
void MacroDefined(Token const& MacroNameTok, MacroDirective const* MD) override { // Record all defined macros. We store the whole token to get info on the
// name later.
this->Macros.emplace_back(MacroNameTok, MD->getMacroInfo()); }
void Endif(SourceLocation Loc, SourceLocation IfLoc) override { // Record all #endif and the corresponding #ifs (including #ifndefs).
this->EndIfs[IfLoc] = Loc; }
void EndOfMainFile() override { // Now that we have all this information from the preprocessor, use it!
SourceManager& SM = this->PP->getSourceManager();
for (auto const& MacroEntry : this->Macros) { MacroInfo const* MI = MacroEntry.second;
// We use clang's header guard detection. This has the advantage of also
// emitting a warning for cases where a pseudo header guard is found but
// preceded by something blocking the header guard optimization.
if (!MI->isUsedForHeaderGuard()) { continue; }
FileEntryRef FE = *SM.getFileEntryRefForID(SM.getFileID(MI->getDefinitionLoc())); std::string FileName = cleanPath(FE.getName()); this->Files.erase(FileName);
// Look up Locations for this guard.
SourceLocation Ifndef = this->Ifndefs[MacroEntry.first.getIdentifierInfo()].second; SourceLocation Define = MacroEntry.first.getLocation(); SourceLocation EndIf = this ->EndIfs[this->Ifndefs[MacroEntry.first.getIdentifierInfo()].first];
std::vector<FixItHint> FixIts;
HeaderSearch& HeaderInfo = this->PP->getHeaderSearchInfo();
HeaderFileInfo& Info = HeaderInfo.getFileInfo(FE);
DirectiveCache Cache(this->PP, SM.getFileID(MI->getDefinitionLoc())); SourceRange IfndefSrcRange = Cache.createRangeForIfndef(Ifndef); SourceRange DefineSrcRange = Cache.createRangeForDefine(Define); SourceRange EndifSrcRange = Cache.createRangeForEndif(EndIf);
if (Info.isPragmaOnce) { FixIts.push_back(FixItHint::CreateRemoval(IfndefSrcRange)); } else { FixIts.push_back( FixItHint::CreateReplacement(IfndefSrcRange, "#pragma once")); }
FixIts.push_back(FixItHint::CreateRemoval(DefineSrcRange)); FixIts.push_back(FixItHint::CreateRemoval(EndifSrcRange));
this->Check->diag(IfndefSrcRange.getBegin(), "use #pragma once") << FixIts; }
// Emit warnings for headers that are missing guards.
checkGuardlessHeaders(); clearAllState(); }
/// Looks for files that were visited but didn't have a header guard.
/// Emits a warning with fixits suggesting adding one.
void checkGuardlessHeaders() { // Look for header files that didn't have a header guard. Emit a warning
// and fix-its to add the guard.
// TODO: Insert the guard after top comments.
for (auto const& FE : this->Files) { StringRef FileName = FE.getKey(); if (!Check->shouldSuggestToAddPragmaOnce(FileName)) { continue; }
SourceManager& SM = this->PP->getSourceManager(); FileID FID = SM.translateFile(FE.getValue()); SourceLocation StartLoc = SM.getLocForStartOfFile(FID); if (StartLoc.isInvalid()) { continue; }
HeaderSearch& HeaderInfo = this->PP->getHeaderSearchInfo();
HeaderFileInfo& Info = HeaderInfo.getFileInfo(FE.second); if (Info.isPragmaOnce) { continue; }
this->Check->diag(StartLoc, "use #pragma once") << FixItHint::CreateInsertion(StartLoc, "#pragma once\n"); } }
private: void clearAllState() { this->Macros.clear(); this->Files.clear(); this->Ifndefs.clear(); this->EndIfs.clear(); }
std::vector<std::pair<Token, MacroInfo const*>> Macros; llvm::StringMap<FileEntryRef> Files; std::map<IdentifierInfo const*, std::pair<SourceLocation, SourceLocation>> Ifndefs; std::map<SourceLocation, SourceLocation> EndIfs;
Preprocessor* PP; UsePragmaOnceCheck* Check; }; } // namespace
void UsePragmaOnceCheck::storeOptions(ClangTidyOptions::OptionMap& Opts) { this->Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); }
void UsePragmaOnceCheck::registerPPCallbacks(SourceManager const& SM, Preprocessor* PP, Preprocessor* ModuleExpanderPP) { PP->addPPCallbacks(std::make_unique<UsePragmaOncePPCallbacks>(PP, this)); }
bool UsePragmaOnceCheck::shouldSuggestToAddPragmaOnce(StringRef FileName) { return utils::isFileExtension(FileName, this->HeaderFileExtensions); }
} // namespace cmake
} // namespace tidy
} // namespace clang
|