cpp11-migrate: Add a class to support include directives modifications

The IncludeDirectives class helps with detecting and modifying #include
directives. For now it allows the users to add angled-includes in a source file.
This is a start for this class that will evolve in the future to add more
functionality.

This should fix the reverted commit r189037 (buildbot failures on Windows).

llvm-svn: 189354
This commit is contained in:
Guillaume Papin 2013-08-27 14:50:26 +00:00
parent 4c6e00595b
commit 64feb39b1b
6 changed files with 1040 additions and 7 deletions

View File

@ -8,6 +8,7 @@ add_clang_library(migrateCore
IncludeExcludeInfo.cpp
PerfSupport.cpp
Reformatting.cpp
IncludeDirectives.cpp
)
target_link_libraries(migrateCore
clangFormat

View File

@ -0,0 +1,474 @@
//===-- Core/IncludeDirectives.cpp - Include directives handling ----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file defines the IncludeDirectives class that helps with
/// detecting and modifying \#include directives.
///
//===----------------------------------------------------------------------===//
#include "IncludeDirectives.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include <stack>
using namespace clang;
using namespace clang::tooling;
using llvm::StringRef;
/// \brief PPCallbacks that fills-in the include information in the given
/// \c IncludeDirectives.
class IncludeDirectivesPPCallback : public clang::PPCallbacks {
// Struct helping the detection of header guards in the various callbacks
struct GuardDetection {
GuardDetection(FileID FID)
: FID(FID), Count(0), TheMacro(0), CountAtEndif(0) {}
FileID FID;
// count for relevant preprocessor directives
unsigned Count;
// the macro that is tested in the top most ifndef for the header guard
// (e.g: GUARD_H)
const IdentifierInfo *TheMacro;
// the hash locations of #ifndef, #define, #endif
SourceLocation IfndefLoc, DefineLoc, EndifLoc;
// the value of Count once the #endif is reached
unsigned CountAtEndif;
/// \brief Check that with all the information gathered if this is a
/// potential header guard.
///
/// Meaning a top-most \#ifndef has been found, followed by a define and the
/// last preprocessor directive was the terminating \#endif.
///
/// FIXME: accept the \#if !defined identifier form too.
bool isPotentialHeaderGuard() const {
return Count == CountAtEndif && DefineLoc.isValid();
}
};
public:
IncludeDirectivesPPCallback(IncludeDirectives *Self) : Self(Self), Guard(0) {}
private:
virtual ~IncludeDirectivesPPCallback() {}
void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
StringRef FileName, bool IsAngled,
CharSourceRange FilenameRange, const FileEntry *File,
StringRef SearchPath, StringRef RelativePath,
const Module *Imported) LLVM_OVERRIDE {
SourceManager &SM = Self->Sources;
const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(HashLoc));
assert(FE && "Valid file expected.");
IncludeDirectives::Entry E(HashLoc, File, IsAngled);
Self->FileToEntries[FE].push_back(E);
Self->IncludeAsWrittenToLocationsMap[FileName].push_back(HashLoc);
}
// Keep track of the current file in the stack
virtual void FileChanged(SourceLocation Loc, FileChangeReason Reason,
SrcMgr::CharacteristicKind FileType,
FileID PrevFID) {
SourceManager &SM = Self->Sources;
switch (Reason) {
case EnterFile:
Files.push(GuardDetection(SM.getFileID(Loc)));
Guard = &Files.top();
break;
case ExitFile:
if (Guard->isPotentialHeaderGuard())
handlePotentialHeaderGuard(*Guard);
Files.pop();
Guard = &Files.top();
break;
default:
break;
}
}
/// \brief Mark this header as guarded in the IncludeDirectives if it's a
/// proper header guard.
void handlePotentialHeaderGuard(const GuardDetection &Guard) {
SourceManager &SM = Self->Sources;
const FileEntry *File = SM.getFileEntryForID(Guard.FID);
const LangOptions &LangOpts = Self->CI.getLangOpts();
// Null file can happen for the <built-in> buffer for example. They
// shouldn't have header guards though...
if (!File)
return;
// The #ifndef should be the next thing after the preamble. We aren't
// checking for equality because it can also be part of the preamble if the
// preamble is the whole file.
unsigned Preamble =
Lexer::ComputePreamble(SM.getBuffer(Guard.FID), LangOpts).first;
unsigned IfndefOffset = SM.getFileOffset(Guard.IfndefLoc);
if (IfndefOffset > (Preamble + 1))
return;
// No code is allowed in the code remaining after the #endif.
const llvm::MemoryBuffer *Buffer = SM.getBuffer(Guard.FID);
Lexer Lex(SM.getLocForStartOfFile(Guard.FID), LangOpts,
Buffer->getBufferStart(),
Buffer->getBufferStart() + SM.getFileOffset(Guard.EndifLoc),
Buffer->getBufferEnd());
// Find the first newline not part of a multi-line comment.
Token Tok;
Lex.LexFromRawLexer(Tok); // skip endif
Lex.LexFromRawLexer(Tok);
// Not a proper header guard, the remainder of the file contains something
// else than comments or whitespaces.
if (Tok.isNot(tok::eof))
return;
// Add to the location of the define to the IncludeDirectives for this file.
Self->HeaderToGuard[File] = Guard.DefineLoc;
}
virtual void Ifndef(SourceLocation Loc, const Token &MacroNameTok,
const MacroDirective *MD) {
Guard->Count++;
// If this #ifndef is the top-most directive and the symbol isn't defined
// store those information in the guard detection, the next step will be to
// check for the define.
if (Guard->Count == 1 && MD == 0) {
IdentifierInfo *MII = MacroNameTok.getIdentifierInfo();
if (MII->hasMacroDefinition())
return;
Guard->IfndefLoc = Loc;
Guard->TheMacro = MII;
}
}
virtual void MacroDefined(const Token &MacroNameTok,
const MacroDirective *MD) {
Guard->Count++;
// If this #define is the second directive of the file and the symbol
// defined is the same as the one checked in the #ifndef then store the
// information about this define.
if (Guard->Count == 2 && Guard->TheMacro != 0) {
IdentifierInfo *MII = MacroNameTok.getIdentifierInfo();
// macro unrelated to the ifndef, doesn't look like a proper header guard
if (MII->getName() != Guard->TheMacro->getName())
return;
Guard->DefineLoc = MacroNameTok.getLocation();
}
}
virtual void Endif(SourceLocation Loc, SourceLocation IfLoc) {
Guard->Count++;
// If it's the #endif corresponding to the top-most #ifndef
if (Self->Sources.getDecomposedLoc(Guard->IfndefLoc) !=
Self->Sources.getDecomposedLoc(IfLoc))
return;
// And that the top-most #ifndef was followed by the right #define
if (Guard->DefineLoc.isInvalid())
return;
// Then save the information about this #endif. Once the file is exited we
// will check if it was the final preprocessor directive.
Guard->CountAtEndif = Guard->Count;
Guard->EndifLoc = Loc;
}
virtual void MacroExpands(const Token &, const MacroDirective *, SourceRange,
const MacroArgs *) {
Guard->Count++;
}
virtual void MacroUndefined(const Token &, const MacroDirective *) {
Guard->Count++;
}
virtual void Defined(const Token &, const MacroDirective *, SourceRange) {
Guard->Count++;
}
virtual void If(SourceLocation, SourceRange, bool) { Guard->Count++; }
virtual void Elif(SourceLocation, SourceRange, bool, SourceLocation) {
Guard->Count++;
}
virtual void Ifdef(SourceLocation, const Token &, const MacroDirective *) {
Guard->Count++;
}
virtual void Else(SourceLocation, SourceLocation) { Guard->Count++; }
IncludeDirectives *Self;
// keep track of the guard info through the include stack
std::stack<GuardDetection> Files;
// convenience field pointing to Files.top().second
GuardDetection *Guard;
};
// Flags that describes where to insert newlines.
enum NewLineFlags {
// Prepend a newline at the beginning of the insertion.
NL_Prepend = 0x1,
// Prepend another newline at the end of the insertion.
NL_PrependAnother = 0x2,
// Add two newlines at the end of the insertion.
NL_AppendTwice = 0x4,
// Convenience value to enable both \c NL_Prepend and \c NL_PrependAnother.
NL_PrependTwice = NL_Prepend | NL_PrependAnother
};
/// \brief Guess the end-of-line sequence used in the given FileID. If the
/// sequence can't be guessed return an Unix-style newline.
static StringRef guessEOL(SourceManager &SM, FileID ID) {
StringRef Content = SM.getBufferData(ID);
StringRef Buffer = Content.substr(Content.find_first_of("\r\n"));
return llvm::StringSwitch<StringRef>(Buffer)
.StartsWith("\r\n", "\r\n")
.StartsWith("\n\r", "\n\r")
.StartsWith("\r", "\r")
.Default("\n");
}
/// \brief Find the end of the end of the directive, either the beginning of a
/// newline or the end of file.
//
// \return The offset into the file where the directive ends along with a
// boolean value indicating whether the directive ends because the end of file
// was reached or not.
static std::pair<unsigned, bool> findDirectiveEnd(SourceLocation HashLoc,
SourceManager &SM,
const LangOptions &LangOpts) {
FileID FID = SM.getFileID(HashLoc);
unsigned Offset = SM.getFileOffset(HashLoc);
StringRef Content = SM.getBufferData(FID);
Lexer Lex(SM.getLocForStartOfFile(FID), LangOpts, Content.begin(),
Content.begin() + Offset, Content.end());
Lex.SetCommentRetentionState(true);
Token Tok;
// This loop look for the newline after our directive but avoids the ones part
// of a multi-line comments:
//
// #include <foo> /* long \n comment */\n
// ~~ no ~~ yes
for (;;) {
// find the beginning of the end-of-line sequence
StringRef::size_type EOLOffset = Content.find_first_of("\r\n", Offset);
// ends because EOF was reached
if (EOLOffset == StringRef::npos)
return std::make_pair(Content.size(), true);
// find the token that contains our end-of-line
unsigned TokEnd = 0;
do {
Lex.LexFromRawLexer(Tok);
TokEnd = SM.getFileOffset(Tok.getLocation()) + Tok.getLength();
// happens when the whitespaces are eaten after a multiline comment
if (Tok.is(tok::eof))
return std::make_pair(EOLOffset, false);
} while (TokEnd < EOLOffset);
// the end-of-line is not part of a multi-line comment, return its location
if (Tok.isNot(tok::comment))
return std::make_pair(EOLOffset, false);
// for the next search to start after the end of this token
Offset = TokEnd;
}
}
IncludeDirectives::IncludeDirectives(clang::CompilerInstance &CI)
: CI(CI), Sources(CI.getSourceManager()) {
// addPPCallbacks takes ownership of the callback
CI.getPreprocessor().addPPCallbacks(new IncludeDirectivesPPCallback(this));
}
bool IncludeDirectives::lookForInclude(const FileEntry *File,
const LocationVec &IncludeLocs,
SeenFilesSet &Seen) const {
// mark this file as visited
Seen.insert(File);
// First check if included directly in this file
for (LocationVec::const_iterator I = IncludeLocs.begin(),
E = IncludeLocs.end();
I != E; ++I)
if (Sources.getFileEntryForID(Sources.getFileID(*I)) == File)
return true;
// Otherwise look recursively all the included files
FileToEntriesMap::const_iterator EntriesIt = FileToEntries.find(File);
if (EntriesIt == FileToEntries.end())
return false;
for (EntryVec::const_iterator I = EntriesIt->second.begin(),
E = EntriesIt->second.end();
I != E; ++I) {
// skip if this header has already been checked before
if (Seen.count(I->getIncludedFile()))
continue;
if (lookForInclude(I->getIncludedFile(), IncludeLocs, Seen))
return true;
}
return false;
}
bool IncludeDirectives::hasInclude(const FileEntry *File,
StringRef Include) const {
llvm::StringMap<LocationVec>::const_iterator It =
IncludeAsWrittenToLocationsMap.find(Include);
// Include isn't included in any file
if (It == IncludeAsWrittenToLocationsMap.end())
return false;
SeenFilesSet Seen;
return lookForInclude(File, It->getValue(), Seen);
}
Replacement IncludeDirectives::addAngledInclude(const clang::FileEntry *File,
llvm::StringRef Include) {
FileID FID = Sources.translateFile(File);
assert(!FID.isInvalid() && "Invalid file entry given!");
if (hasInclude(File, Include))
return Replacement();
unsigned Offset, NLFlags;
llvm::tie(Offset, NLFlags) = angledIncludeInsertionOffset(FID);
StringRef EOL = guessEOL(Sources, FID);
llvm::SmallString<32> InsertionText;
if (NLFlags & NL_Prepend)
InsertionText += EOL;
if (NLFlags & NL_PrependAnother)
InsertionText += EOL;
InsertionText += "#include <";
InsertionText += Include;
InsertionText += ">";
if (NLFlags & NL_AppendTwice) {
InsertionText += EOL;
InsertionText += EOL;
}
return Replacement(File->getName(), Offset, 0, InsertionText);
}
Replacement IncludeDirectives::addAngledInclude(llvm::StringRef File,
llvm::StringRef Include) {
const FileEntry *Entry = Sources.getFileManager().getFile(File);
assert(Entry && "Invalid file given!");
return addAngledInclude(Entry, Include);
}
std::pair<unsigned, unsigned>
IncludeDirectives::findFileHeaderEndOffset(FileID FID) const {
unsigned NLFlags = NL_Prepend;
StringRef Content = Sources.getBufferData(FID);
Lexer Lex(Sources.getLocForStartOfFile(FID), CI.getLangOpts(),
Content.begin(), Content.begin(), Content.end());
Lex.SetCommentRetentionState(true);
Lex.SetKeepWhitespaceMode(true);
// find the first newline not part of a multi-line comment
Token Tok;
do {
Lex.LexFromRawLexer(Tok);
unsigned Offset = Sources.getFileOffset(Tok.getLocation());
// allow one newline between the comments
if (Tok.is(tok::unknown) && isWhitespace(Content[Offset])) {
StringRef Whitespaces(Content.substr(Offset, Tok.getLength()));
if (Whitespaces.count('\n') == 1 || Whitespaces.count('\r') == 1)
Lex.LexFromRawLexer(Tok);
else {
// add an empty line to separate the file header and the inclusion
NLFlags = NL_PrependTwice;
}
}
} while (Tok.is(tok::comment));
// apparently there is no header, insertion point is the beginning of the file
if (Tok.isNot(tok::unknown))
return std::make_pair(0, NL_AppendTwice);
return std::make_pair(Sources.getFileOffset(Tok.getLocation()), NLFlags);
}
SourceLocation
IncludeDirectives::angledIncludeHintLoc(FileID FID) const {
FileToEntriesMap::const_iterator EntriesIt =
FileToEntries.find(Sources.getFileEntryForID(FID));
if (EntriesIt == FileToEntries.end())
return SourceLocation();
HeaderSearch &HeaderInfo = CI.getPreprocessor().getHeaderSearchInfo();
const EntryVec &Entries = EntriesIt->second;
EntryVec::const_reverse_iterator QuotedCandidate = Entries.rend();
for (EntryVec::const_reverse_iterator I = Entries.rbegin(),
E = Entries.rend();
I != E; ++I) {
// Headers meant for multiple inclusion can potentially appears in the
// middle of the code thus making them a poor choice for an insertion point.
if (!HeaderInfo.isFileMultipleIncludeGuarded(I->getIncludedFile()))
continue;
// return preferably the last angled include
if (I->isAngled())
return I->getHashLocation();
// keep track of the last quoted include that is guarded
if (QuotedCandidate == Entries.rend())
QuotedCandidate = I;
}
if (QuotedCandidate == Entries.rend())
return SourceLocation();
// return the last quoted-include if we couldn't find an angled one
return QuotedCandidate->getHashLocation();
}
std::pair<unsigned, unsigned>
IncludeDirectives::angledIncludeInsertionOffset(FileID FID) const {
SourceLocation Hint = angledIncludeHintLoc(FID);
unsigned NL_Flags = NL_Prepend;
// If we can't find a similar include and we are in a header check if it's a
// guarded header. If so the hint will be the location of the #define from the
// guard.
if (Hint.isInvalid()) {
const FileEntry *File = Sources.getFileEntryForID(FID);
HeaderToGuardMap::const_iterator GuardIt = HeaderToGuard.find(File);
if (GuardIt != HeaderToGuard.end()) {
// get the hash location from the #define
Hint = GuardIt->second;
// we want a blank line between the #define and the #include
NL_Flags = NL_PrependTwice;
}
}
// no hints, insertion is done after the file header
if (Hint.isInvalid())
return findFileHeaderEndOffset(FID);
unsigned Offset = findDirectiveEnd(Hint, Sources, CI.getLangOpts()).first;
return std::make_pair(Offset, NL_Flags);
}

View File

@ -0,0 +1,141 @@
//===-- Core/IncludeDirectives.h - Include directives handling --*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file declares the IncludeDirectives class that helps with
/// detecting and modifying \#include directives.
///
//===----------------------------------------------------------------------===//
#ifndef CPP11_MIGRATE_INCLUDE_DIRECTIVES_H
#define CPP11_MIGRATE_INCLUDE_DIRECTIVES_H
#include "clang/Basic/SourceLocation.h"
#include "clang/Tooling/Refactoring.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/SmallPtrSet.h"
#include <vector>
namespace clang {
class Preprocessor;
} // namespace clang
/// \brief Support for include directives handling.
///
/// This class should be created with a \c clang::CompilerInstance before the
/// file is preprocessed in order to collect the inclusion information. It can
/// be queried as long as the compiler instance is valid.
class IncludeDirectives {
public:
IncludeDirectives(clang::CompilerInstance &CI);
/// \brief Add an angled include to a the given file.
///
/// \param File A file accessible by a SourceManager
/// \param Include The include file as it should be written in the code.
///
/// \returns
/// \li A null Replacement (check using \c Replacement::isApplicable()), if
/// the \c Include is already visible from \c File.
/// \li Otherwise, a non-null Replacement that, when applied, inserts an
/// \c \#include into \c File.
clang::tooling::Replacement addAngledInclude(llvm::StringRef File,
llvm::StringRef Include);
clang::tooling::Replacement addAngledInclude(const clang::FileEntry *File,
llvm::StringRef Include);
/// \brief Check if \p Include is included by \p File or any of the files
/// \p File includes.
bool hasInclude(const clang::FileEntry *File, llvm::StringRef Include) const;
private:
friend class IncludeDirectivesPPCallback;
/// \brief Contains information about an inclusion.
class Entry {
public:
Entry(clang::SourceLocation HashLoc, const clang::FileEntry *IncludedFile,
bool Angled)
: HashLoc(HashLoc), IncludedFile(IncludedFile), Angled(Angled) {}
/// \brief The location of the '#'.
clang::SourceLocation getHashLocation() const { return HashLoc; }
/// \brief The file included by this include directive.
const clang::FileEntry *getIncludedFile() const { return IncludedFile; }
/// \brief \c true if the include use angle brackets, \c false otherwise
/// when using of quotes.
bool isAngled() const { return Angled; }
private:
clang::SourceLocation HashLoc;
const clang::FileEntry *IncludedFile;
bool Angled;
};
// A list of entries.
typedef std::vector<Entry> EntryVec;
// A list of source locations.
typedef std::vector<clang::SourceLocation> LocationVec;
// Associates files to their includes.
typedef llvm::DenseMap<const clang::FileEntry *, EntryVec> FileToEntriesMap;
// Associates headers to their include guards if any. The location is the
// location of the hash from the #define.
typedef llvm::DenseMap<const clang::FileEntry *, clang::SourceLocation>
HeaderToGuardMap;
/// \brief Type used by \c lookForInclude() to keep track of the files that
/// have already been processed.
typedef llvm::SmallPtrSet<const clang::FileEntry *, 32> SeenFilesSet;
/// \brief Recursively look if an include is included by \p File or any of the
/// headers \p File includes.
///
/// \param File The file where to start the search.
/// \param IncludeLocs These are the hash locations of the \#include
/// directives we are looking for.
/// \param Seen Used to avoid visiting a same file more than once during the
/// recursion.
bool lookForInclude(const clang::FileEntry *File,
const LocationVec &IncludeLocs, SeenFilesSet &Seen) const;
/// \brief Find the end of a file header and returns a pair (FileOffset,
/// NewLineFlags).
///
/// Source files often contain a file header (copyright, license, explanation
/// of the file content). An \#include should preferrably be put after this.
std::pair<unsigned, unsigned>
findFileHeaderEndOffset(clang::FileID FID) const;
/// \brief Finds the offset where an angled include should be added and
/// returns a pair (FileOffset, NewLineFlags).
std::pair<unsigned, unsigned>
angledIncludeInsertionOffset(clang::FileID FID) const;
/// \brief Find the location of an include directive that can be used to
/// insert an inclusion after.
///
/// If no such include exists returns a null SourceLocation.
clang::SourceLocation angledIncludeHintLoc(clang::FileID FID) const;
clang::CompilerInstance &CI;
clang::SourceManager &Sources;
FileToEntriesMap FileToEntries;
// maps include filename as written in the source code to the source locations
// where it appears
llvm::StringMap<LocationVec> IncludeAsWrittenToLocationsMap;
HeaderToGuardMap HeaderToGuard;
};
#endif // CPP11_MIGRATE_INCLUDE_DIRECTIVES_H

View File

@ -13,6 +13,7 @@ add_extra_unittest(Cpp11MigrateTests
PerfSupportTest.cpp
TransformTest.cpp
UniqueHeaderNameTest.cpp
IncludeDirectivesTest.cpp
)
target_link_libraries(Cpp11MigrateTests

View File

@ -0,0 +1,410 @@
//===- cpp11-migrate/IncludeDirectivesTest.cpp ----------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "Core/IncludeDirectives.h"
#include "gtest/gtest.h"
#include "VirtualFileHelper.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "llvm/Support/Path.h"
using namespace llvm;
using namespace clang;
/// \brief A convenience method around \c tooling::runToolOnCodeWithArgs() that
/// adds the current directory to the include search paths.
static void applyActionOnCode(FrontendAction *ToolAction, StringRef Code) {
SmallString<128> CurrentDir;
ASSERT_FALSE(llvm::sys::fs::current_path(CurrentDir));
// Add the current directory to the header search paths so angled includes can
// find them.
std::vector<std::string> Args;
Args.push_back("-I");
Args.push_back(CurrentDir.str().str());
// mapVirtualFile() needs absolute path for the input file as well.
SmallString<128> InputFile(CurrentDir);
sys::path::append(InputFile, "input.cc");
ASSERT_TRUE(
tooling::runToolOnCodeWithArgs(ToolAction, Code, Args, InputFile.str()));
}
namespace {
class TestAddIncludeAction : public PreprocessOnlyAction {
public:
TestAddIncludeAction(StringRef Include, tooling::Replacements &Replaces,
const char *HeaderToModify = 0)
: Include(Include), Replaces(Replaces), HeaderToModify(HeaderToModify) {
// some headers that the tests can include
mapVirtualHeader("foo-inner.h", "#pragma once\n");
mapVirtualHeader("foo.h", "#pragma once\n"
"#include <foo-inner.h>\n");
mapVirtualHeader("bar-inner.h", "#pragma once\n");
mapVirtualHeader("bar.h", "#pragma once\n"
"#include <bar-inner.h>\n");
mapVirtualHeader("xmacro.def", "X(Val1)\n"
"X(Val2)\n"
"X(Val3)\n");
}
/// \brief Make \p FileName an absolute path.
///
/// Header files are mapped in the current working directory. The current
/// working directory is used because it's important to map files with
/// absolute paths.
///
/// When used in conjunction with \c applyActionOnCode() (which adds the
/// current working directory to the header search paths) it is possible to
/// refer to the headers by using '\<FileName\>'.
std::string makeHeaderFileName(StringRef FileName) const {
SmallString<128> Path;
llvm::error_code EC = llvm::sys::fs::current_path(Path);
assert(!EC);
(void)EC;
sys::path::append(Path, FileName);
return Path.str().str();
}
/// \brief Map additional header files.
///
/// \sa makeHeaderFileName()
void mapVirtualHeader(StringRef FileName, StringRef Content) {
VFHelper.mapFile(makeHeaderFileName(FileName), Content);
}
private:
virtual bool BeginSourceFileAction(CompilerInstance &CI,
StringRef FileName) LLVM_OVERRIDE {
if (!PreprocessOnlyAction::BeginSourceFileAction(CI, FileName))
return false;
VFHelper.mapVirtualFiles(CI.getSourceManager());
FileToModify =
HeaderToModify ? makeHeaderFileName(HeaderToModify) : FileName.str();
FileIncludes.reset(new IncludeDirectives(CI));
return true;
}
virtual void EndSourceFileAction() LLVM_OVERRIDE {
const tooling::Replacement &Replace =
FileIncludes->addAngledInclude(FileToModify, Include);
if (Replace.isApplicable())
Replaces.insert(Replace);
}
StringRef Include;
VirtualFileHelper VFHelper;
tooling::Replacements &Replaces;
OwningPtr<IncludeDirectives> FileIncludes;
std::string FileToModify;
// if non-null, add the include directives in this file instead of the main
// file.
const char *HeaderToModify;
};
std::string addIncludeInCode(StringRef Include, StringRef Code) {
tooling::Replacements Replaces;
applyActionOnCode(new TestAddIncludeAction(Include, Replaces), Code);
if (::testing::Test::HasFailure())
return "<<unexpected error from applyActionOnCode()>>";
return tooling::applyAllReplacements(Code, Replaces);
}
} // end anonymous namespace
TEST(IncludeDirectivesTest2, endOfLinesVariants) {
EXPECT_EQ("#include <foo.h>\n"
"#include <bar>\n",
addIncludeInCode("bar", "#include <foo.h>\n"));
EXPECT_EQ("#include <foo.h>\r\n"
"#include <bar>\r\n",
addIncludeInCode("bar", "#include <foo.h>\r\n"));
EXPECT_EQ("#include <foo.h>\r"
"#include <bar>\r",
addIncludeInCode("bar", "#include <foo.h>\r"));
}
TEST(IncludeDirectivesTest, ppToken) {
EXPECT_EQ("#define FOO <foo.h>\n"
"#include FOO\n"
"#include <bar>\n"
"int i;\n",
addIncludeInCode("bar", "#define FOO <foo.h>\n"
"#include FOO\n"
"int i;\n"));
}
TEST(IncludeDirectivesTest, noFileHeader) {
EXPECT_EQ("#include <bar>\n"
"\n"
"int foo;\n",
addIncludeInCode("bar", "int foo;\n"));
}
TEST(IncludeDirectivesTest, commentBeforeTopMostCode) {
EXPECT_EQ("#include <bar>\n"
"\n"
"// Foo\n"
"int foo;\n",
addIncludeInCode("bar", "// Foo\n"
"int foo;\n"));
}
TEST(IncludeDirectivesTest, multiLineComment) {
EXPECT_EQ("#include <foo.h> /* \n */\n"
"#include <bar>\n",
addIncludeInCode("bar", "#include <foo.h> /* \n */\n"));
EXPECT_EQ("#include <foo.h> /* \n */"
"\n#include <bar>",
addIncludeInCode("bar", "#include <foo.h> /* \n */"));
}
TEST(IncludeDirectivesTest, multilineCommentWithTrailingSpace) {
EXPECT_EQ("#include <foo.h> /*\n*/ \n"
"#include <bar>\n",
addIncludeInCode("bar", "#include <foo.h> /*\n*/ \n"));
EXPECT_EQ("#include <foo.h> /*\n*/ "
"\n#include <bar>",
addIncludeInCode("bar", "#include <foo.h> /*\n*/ "));
}
TEST(IncludeDirectivesTest, fileHeaders) {
EXPECT_EQ("// this is a header\n"
"// some license stuff here\n"
"\n"
"#include <bar>\n"
"\n"
"/// \\brief Foo\n"
"int foo;\n",
addIncludeInCode("bar", "// this is a header\n"
"// some license stuff here\n"
"\n"
"/// \\brief Foo\n"
"int foo;\n"));
}
TEST(IncludeDirectivesTest, preferablyAngledNextToAngled) {
EXPECT_EQ("#include <foo.h>\n"
"#include <bar>\n"
"#include \"bar.h\"\n",
addIncludeInCode("bar", "#include <foo.h>\n"
"#include \"bar.h\"\n"));
EXPECT_EQ("#include \"foo.h\"\n"
"#include \"bar.h\"\n"
"#include <bar>\n",
addIncludeInCode("bar", "#include \"foo.h\"\n"
"#include \"bar.h\"\n"));
}
TEST(IncludeDirectivesTest, avoidDuplicates) {
EXPECT_EQ("#include <foo.h>\n",
addIncludeInCode("foo.h", "#include <foo.h>\n"));
}
// Tests includes in the middle of the code are ignored.
TEST(IncludeDirectivesTest, ignoreHeadersMeantForMultipleInclusion) {
std::string Expected = "#include \"foo.h\"\n"
"#include <bar>\n"
"\n"
"enum Kind {\n"
"#define X(A) K_##A,\n"
"#include \"xmacro.def\"\n"
"#undef X\n"
" K_NUM_KINDS\n"
"};\n";
std::string Result = addIncludeInCode("bar", "#include \"foo.h\"\n"
"\n"
"enum Kind {\n"
"#define X(A) K_##A,\n"
"#include \"xmacro.def\"\n"
"#undef X\n"
" K_NUM_KINDS\n"
"};\n");
EXPECT_EQ(Expected, Result);
}
namespace {
TestAddIncludeAction *makeIndirectTestsAction(const char *HeaderToModify,
tooling::Replacements &Replaces) {
StringRef IncludeToAdd = "c.h";
TestAddIncludeAction *TestAction =
new TestAddIncludeAction(IncludeToAdd, Replaces, HeaderToModify);
TestAction->mapVirtualHeader("c.h", "#pragma once\n");
TestAction->mapVirtualHeader("a.h", "#pragma once\n"
"#include <c.h>\n");
TestAction->mapVirtualHeader("b.h", "#pragma once\n");
return TestAction;
}
} // end anonymous namespace
TEST(IncludeDirectivesTest, indirectIncludes) {
// In TestAddIncludeAction 'foo.h' includes 'foo-inner.h'. Check that we
// aren't including foo-inner.h again.
EXPECT_EQ("#include <foo.h>\n",
addIncludeInCode("foo-inner.h", "#include <foo.h>\n"));
tooling::Replacements Replaces;
StringRef Code = "#include <a.h>\n"
"#include <b.h>\n";
// a.h already includes c.h
{
FrontendAction *Action = makeIndirectTestsAction("a.h", Replaces);
ASSERT_NO_FATAL_FAILURE(applyActionOnCode(Action, Code));
EXPECT_EQ(unsigned(0), Replaces.size());
}
// c.h is included before b.h but b.h doesn't include c.h directly, so check
// that it will be inserted.
{
FrontendAction *Action = makeIndirectTestsAction("b.h", Replaces);
ASSERT_NO_FATAL_FAILURE(applyActionOnCode(Action, Code));
EXPECT_EQ("#include <c.h>\n\n\n",
tooling::applyAllReplacements("\n", Replaces));
}
}
/// \brief Convenience method to test header guards detection implementation.
static std::string addIncludeInGuardedHeader(StringRef IncludeToAdd,
StringRef GuardedHeaderCode) {
const char *GuardedHeaderName = "guarded.h";
tooling::Replacements Replaces;
TestAddIncludeAction *TestAction =
new TestAddIncludeAction(IncludeToAdd, Replaces, GuardedHeaderName);
TestAction->mapVirtualHeader(GuardedHeaderName, GuardedHeaderCode);
applyActionOnCode(TestAction, "#include <guarded.h>\n");
if (::testing::Test::HasFailure())
return "<<unexpected error from applyActionOnCode()>>";
return tooling::applyAllReplacements(GuardedHeaderCode, Replaces);
}
TEST(IncludeDirectivesTest, insertInsideIncludeGuard) {
EXPECT_EQ("#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"#include <foo>\n"
"\n"
"struct foo {};\n"
"\n"
"#endif // GUARD_H\n",
addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"struct foo {};\n"
"\n"
"#endif // GUARD_H\n"));
}
TEST(IncludeDirectivesTest, guardAndHeader) {
EXPECT_EQ("// File header\n"
"\n"
"#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"#include <foo>\n"
"\n"
"struct foo {};\n"
"\n"
"#endif // GUARD_H\n",
addIncludeInGuardedHeader("foo", "// File header\n"
"\n"
"#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"struct foo {};\n"
"\n"
"#endif // GUARD_H\n"));
}
TEST(IncludeDirectivesTest, fullHeaderFitsAsAPreamble) {
EXPECT_EQ("#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"#include <foo>\n"
"\n"
"#define FOO 1\n"
"\n"
"#endif // GUARD_H\n",
addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"#define FOO 1\n"
"\n"
"#endif // GUARD_H\n"));
}
TEST(IncludeDirectivesTest, codeBeforeIfndef) {
EXPECT_EQ("#include <foo>\n"
"\n"
"int bar;\n"
"\n"
"#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"struct foo;"
"\n"
"#endif // GUARD_H\n",
addIncludeInGuardedHeader("foo", "int bar;\n"
"\n"
"#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"struct foo;"
"\n"
"#endif // GUARD_H\n"));
}
TEST(IncludeDirectivesTest, codeAfterEndif) {
EXPECT_EQ("#include <foo>\n"
"\n"
"#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"struct foo;"
"\n"
"#endif // GUARD_H\n"
"\n"
"int bar;\n",
addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"struct foo;"
"\n"
"#endif // GUARD_H\n"
"\n"
"int bar;\n"));
}
TEST(IncludeDirectivesTest, headerGuardWithInclude) {
EXPECT_EQ("#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"#include <bar.h>\n"
"#include <foo>\n"
"\n"
"struct foo;\n"
"\n"
"#endif // GUARD_H\n",
addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n"
"#define GUARD_H\n"
"\n"
"#include <bar.h>\n"
"\n"
"struct foo;\n"
"\n"
"#endif // GUARD_H\n"));
}

View File

@ -27,8 +27,8 @@ namespace clang {
/// map virtual files conveniently.
class VirtualFileHelper {
struct VirtualFile {
llvm::StringRef FileName;
llvm::StringRef Code;
std::string FileName;
std::string Code;
};
public:
@ -49,15 +49,21 @@ public:
/// mapped to it.
SourceManager &getNewSourceManager() {
Sources.reset(new SourceManager(Diagnostics, Files));
for (llvm::SmallVectorImpl<VirtualFile>::iterator I = VirtualFiles.begin(),
E = VirtualFiles.end();
mapVirtualFiles(*Sources);
return *Sources;
}
/// \brief Map the virtual file contents in the given \c SourceManager.
void mapVirtualFiles(SourceManager &SM) const {
for (llvm::SmallVectorImpl<VirtualFile>::const_iterator
I = VirtualFiles.begin(),
E = VirtualFiles.end();
I != E; ++I) {
llvm::MemoryBuffer *Buf = llvm::MemoryBuffer::getMemBuffer(I->Code);
const FileEntry *Entry = Files.getVirtualFile(
const FileEntry *Entry = SM.getFileManager().getVirtualFile(
I->FileName, Buf->getBufferSize(), /*ModificationTime=*/0);
Sources->overrideFileContents(Entry, Buf);
SM.overrideFileContents(Entry, Buf);
}
return *Sources;
}
private: