[clang-tools-extra] r189354 - cpp11-migrate: Add a class to support include directives modifications

Guillaume Papin guillaume.papin at epitech.eu
Tue Aug 27 07:50:27 PDT 2013


Author: papin_g
Date: Tue Aug 27 09:50:26 2013
New Revision: 189354

URL: http://llvm.org/viewvc/llvm-project?rev=189354&view=rev
Log:
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).

Added:
    clang-tools-extra/trunk/cpp11-migrate/Core/IncludeDirectives.cpp
    clang-tools-extra/trunk/cpp11-migrate/Core/IncludeDirectives.h
    clang-tools-extra/trunk/unittests/cpp11-migrate/IncludeDirectivesTest.cpp
Modified:
    clang-tools-extra/trunk/cpp11-migrate/Core/CMakeLists.txt
    clang-tools-extra/trunk/unittests/cpp11-migrate/CMakeLists.txt
    clang-tools-extra/trunk/unittests/cpp11-migrate/VirtualFileHelper.h

Modified: clang-tools-extra/trunk/cpp11-migrate/Core/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/cpp11-migrate/Core/CMakeLists.txt?rev=189354&r1=189353&r2=189354&view=diff
==============================================================================
--- clang-tools-extra/trunk/cpp11-migrate/Core/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/cpp11-migrate/Core/CMakeLists.txt Tue Aug 27 09:50:26 2013
@@ -8,6 +8,7 @@ add_clang_library(migrateCore
   IncludeExcludeInfo.cpp
   PerfSupport.cpp
   Reformatting.cpp
+  IncludeDirectives.cpp
   )
 target_link_libraries(migrateCore
   clangFormat

Added: clang-tools-extra/trunk/cpp11-migrate/Core/IncludeDirectives.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/cpp11-migrate/Core/IncludeDirectives.cpp?rev=189354&view=auto
==============================================================================
--- clang-tools-extra/trunk/cpp11-migrate/Core/IncludeDirectives.cpp (added)
+++ clang-tools-extra/trunk/cpp11-migrate/Core/IncludeDirectives.cpp Tue Aug 27 09:50:26 2013
@@ -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);
+}

Added: clang-tools-extra/trunk/cpp11-migrate/Core/IncludeDirectives.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/cpp11-migrate/Core/IncludeDirectives.h?rev=189354&view=auto
==============================================================================
--- clang-tools-extra/trunk/cpp11-migrate/Core/IncludeDirectives.h (added)
+++ clang-tools-extra/trunk/cpp11-migrate/Core/IncludeDirectives.h Tue Aug 27 09:50:26 2013
@@ -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

Modified: clang-tools-extra/trunk/unittests/cpp11-migrate/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/cpp11-migrate/CMakeLists.txt?rev=189354&r1=189353&r2=189354&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/cpp11-migrate/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/unittests/cpp11-migrate/CMakeLists.txt Tue Aug 27 09:50:26 2013
@@ -13,6 +13,7 @@ add_extra_unittest(Cpp11MigrateTests
   PerfSupportTest.cpp
   TransformTest.cpp
   UniqueHeaderNameTest.cpp
+  IncludeDirectivesTest.cpp
   )
 
 target_link_libraries(Cpp11MigrateTests

Added: clang-tools-extra/trunk/unittests/cpp11-migrate/IncludeDirectivesTest.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/cpp11-migrate/IncludeDirectivesTest.cpp?rev=189354&view=auto
==============================================================================
--- clang-tools-extra/trunk/unittests/cpp11-migrate/IncludeDirectivesTest.cpp (added)
+++ clang-tools-extra/trunk/unittests/cpp11-migrate/IncludeDirectivesTest.cpp Tue Aug 27 09:50:26 2013
@@ -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"));
+}

Modified: clang-tools-extra/trunk/unittests/cpp11-migrate/VirtualFileHelper.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/cpp11-migrate/VirtualFileHelper.h?rev=189354&r1=189353&r2=189354&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/cpp11-migrate/VirtualFileHelper.h (original)
+++ clang-tools-extra/trunk/unittests/cpp11-migrate/VirtualFileHelper.h Tue Aug 27 09:50:26 2013
@@ -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:





More information about the cfe-commits mailing list