You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
10 KiB
324 lines
10 KiB
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt 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();
|
|
const FileEntry* Entry = SM.getFileEntryForID(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](const SourceLocation& 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](const SourceLocation& 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 (const FileEntry* FE = SM.getFileEntryForID(SM.getFileID(Loc))) {
|
|
std::string FileName = cleanPath(FE->getName());
|
|
this->Files[FileName] = FE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Ifndef(SourceLocation Loc, const Token& MacroNameTok,
|
|
const MacroDefinition& 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(const Token& MacroNameTok,
|
|
const MacroDirective* 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 (const auto& MacroEntry : this->Macros) {
|
|
const MacroInfo* 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;
|
|
}
|
|
|
|
const FileEntry* FE =
|
|
SM.getFileEntryForID(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 (const auto& 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, const MacroInfo*>> Macros;
|
|
llvm::StringMap<const FileEntry*> Files;
|
|
std::map<const IdentifierInfo*, 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(const SourceManager& 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
|