aboutsummaryrefslogtreecommitdiff
path: root/lib/Rewrite/Frontend
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Rewrite/Frontend')
-rw-r--r--lib/Rewrite/Frontend/CMakeLists.txt28
-rw-r--r--lib/Rewrite/Frontend/FixItRewriter.cpp205
-rw-r--r--lib/Rewrite/Frontend/FrontendActions.cpp192
-rw-r--r--lib/Rewrite/Frontend/HTMLPrint.cpp94
-rw-r--r--lib/Rewrite/Frontend/InclusionRewriter.cpp361
-rw-r--r--lib/Rewrite/Frontend/Makefile18
-rw-r--r--lib/Rewrite/Frontend/RewriteMacros.cpp217
-rw-r--r--lib/Rewrite/Frontend/RewriteModernObjC.cpp7540
-rw-r--r--lib/Rewrite/Frontend/RewriteObjC.cpp6028
-rw-r--r--lib/Rewrite/Frontend/RewriteTest.cpp39
10 files changed, 14722 insertions, 0 deletions
diff --git a/lib/Rewrite/Frontend/CMakeLists.txt b/lib/Rewrite/Frontend/CMakeLists.txt
new file mode 100644
index 0000000000..9017e479ab
--- /dev/null
+++ b/lib/Rewrite/Frontend/CMakeLists.txt
@@ -0,0 +1,28 @@
+add_clang_library(clangRewriteFrontend
+ FixItRewriter.cpp
+ FrontendActions.cpp
+ HTMLPrint.cpp
+ InclusionRewriter.cpp
+ RewriteMacros.cpp
+ RewriteModernObjC.cpp
+ RewriteObjC.cpp
+ RewriteTest.cpp
+ )
+
+add_dependencies(clangRewriteFrontend
+ ClangAttrClasses
+ ClangAttrList
+ ClangAttrParsedAttrList
+ ClangCommentNodes
+ ClangDeclNodes
+ ClangDiagnosticCommon
+ ClangDiagnosticFrontend
+ ClangStmtNodes
+ )
+
+target_link_libraries(clangRewriteFrontend
+ clangBasic
+ clangAST
+ clangParse
+ clangFrontend
+ )
diff --git a/lib/Rewrite/Frontend/FixItRewriter.cpp b/lib/Rewrite/Frontend/FixItRewriter.cpp
new file mode 100644
index 0000000000..43a1ab1ac1
--- /dev/null
+++ b/lib/Rewrite/Frontend/FixItRewriter.cpp
@@ -0,0 +1,205 @@
+//===--- FixItRewriter.cpp - Fix-It Rewriter Diagnostic Client --*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This is a diagnostic client adaptor that performs rewrites as
+// suggested by code modification hints attached to diagnostics. It
+// then forwards any diagnostics to the adapted diagnostic client.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Rewrite/Frontend/FixItRewriter.h"
+#include "clang/Edit/Commit.h"
+#include "clang/Edit/EditsReceiver.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Frontend/FrontendDiagnostic.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Support/Path.h"
+#include "llvm/ADT/OwningPtr.h"
+#include <cstdio>
+
+using namespace clang;
+
+FixItRewriter::FixItRewriter(DiagnosticsEngine &Diags, SourceManager &SourceMgr,
+ const LangOptions &LangOpts,
+ FixItOptions *FixItOpts)
+ : Diags(Diags),
+ Editor(SourceMgr, LangOpts),
+ Rewrite(SourceMgr, LangOpts),
+ FixItOpts(FixItOpts),
+ NumFailures(0),
+ PrevDiagSilenced(false) {
+ OwnsClient = Diags.ownsClient();
+ Client = Diags.takeClient();
+ Diags.setClient(this);
+}
+
+FixItRewriter::~FixItRewriter() {
+ Diags.takeClient();
+ Diags.setClient(Client, OwnsClient);
+}
+
+bool FixItRewriter::WriteFixedFile(FileID ID, raw_ostream &OS) {
+ const RewriteBuffer *RewriteBuf = Rewrite.getRewriteBufferFor(ID);
+ if (!RewriteBuf) return true;
+ RewriteBuf->write(OS);
+ OS.flush();
+ return false;
+}
+
+namespace {
+
+class RewritesReceiver : public edit::EditsReceiver {
+ Rewriter &Rewrite;
+
+public:
+ RewritesReceiver(Rewriter &Rewrite) : Rewrite(Rewrite) { }
+
+ virtual void insert(SourceLocation loc, StringRef text) {
+ Rewrite.InsertText(loc, text);
+ }
+ virtual void replace(CharSourceRange range, StringRef text) {
+ Rewrite.ReplaceText(range.getBegin(), Rewrite.getRangeSize(range), text);
+ }
+};
+
+}
+
+bool FixItRewriter::WriteFixedFiles(
+ std::vector<std::pair<std::string, std::string> > *RewrittenFiles) {
+ if (NumFailures > 0 && !FixItOpts->FixWhatYouCan) {
+ Diag(FullSourceLoc(), diag::warn_fixit_no_changes);
+ return true;
+ }
+
+ RewritesReceiver Rec(Rewrite);
+ Editor.applyRewrites(Rec);
+
+ for (iterator I = buffer_begin(), E = buffer_end(); I != E; ++I) {
+ const FileEntry *Entry = Rewrite.getSourceMgr().getFileEntryForID(I->first);
+ int fd;
+ std::string Filename = FixItOpts->RewriteFilename(Entry->getName(), fd);
+ std::string Err;
+ OwningPtr<llvm::raw_fd_ostream> OS;
+ if (fd != -1) {
+ OS.reset(new llvm::raw_fd_ostream(fd, /*shouldClose=*/true));
+ } else {
+ OS.reset(new llvm::raw_fd_ostream(Filename.c_str(), Err,
+ llvm::raw_fd_ostream::F_Binary));
+ }
+ if (!Err.empty()) {
+ Diags.Report(clang::diag::err_fe_unable_to_open_output)
+ << Filename << Err;
+ continue;
+ }
+ RewriteBuffer &RewriteBuf = I->second;
+ RewriteBuf.write(*OS);
+ OS->flush();
+
+ if (RewrittenFiles)
+ RewrittenFiles->push_back(std::make_pair(Entry->getName(), Filename));
+ }
+
+ return false;
+}
+
+bool FixItRewriter::IncludeInDiagnosticCounts() const {
+ return Client ? Client->IncludeInDiagnosticCounts() : true;
+}
+
+void FixItRewriter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
+ const Diagnostic &Info) {
+ // Default implementation (Warnings/errors count).
+ DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
+
+ if (!FixItOpts->Silent ||
+ DiagLevel >= DiagnosticsEngine::Error ||
+ (DiagLevel == DiagnosticsEngine::Note && !PrevDiagSilenced) ||
+ (DiagLevel > DiagnosticsEngine::Note && Info.getNumFixItHints())) {
+ Client->HandleDiagnostic(DiagLevel, Info);
+ PrevDiagSilenced = false;
+ } else {
+ PrevDiagSilenced = true;
+ }
+
+ // Skip over any diagnostics that are ignored or notes.
+ if (DiagLevel <= DiagnosticsEngine::Note)
+ return;
+ // Skip over errors if we are only fixing warnings.
+ if (DiagLevel >= DiagnosticsEngine::Error && FixItOpts->FixOnlyWarnings) {
+ ++NumFailures;
+ return;
+ }
+
+ // Make sure that we can perform all of the modifications we
+ // in this diagnostic.
+ edit::Commit commit(Editor);
+ for (unsigned Idx = 0, Last = Info.getNumFixItHints();
+ Idx < Last; ++Idx) {
+ const FixItHint &Hint = Info.getFixItHint(Idx);
+
+ if (Hint.CodeToInsert.empty()) {
+ if (Hint.InsertFromRange.isValid())
+ commit.insertFromRange(Hint.RemoveRange.getBegin(),
+ Hint.InsertFromRange, /*afterToken=*/false,
+ Hint.BeforePreviousInsertions);
+ else
+ commit.remove(Hint.RemoveRange);
+ } else {
+ if (Hint.RemoveRange.isTokenRange() ||
+ Hint.RemoveRange.getBegin() != Hint.RemoveRange.getEnd())
+ commit.replace(Hint.RemoveRange, Hint.CodeToInsert);
+ else
+ commit.insert(Hint.RemoveRange.getBegin(), Hint.CodeToInsert,
+ /*afterToken=*/false, Hint.BeforePreviousInsertions);
+ }
+ }
+ bool CanRewrite = Info.getNumFixItHints() > 0 && commit.isCommitable();
+
+ if (!CanRewrite) {
+ if (Info.getNumFixItHints() > 0)
+ Diag(Info.getLocation(), diag::note_fixit_in_macro);
+
+ // If this was an error, refuse to perform any rewriting.
+ if (DiagLevel >= DiagnosticsEngine::Error) {
+ if (++NumFailures == 1)
+ Diag(Info.getLocation(), diag::note_fixit_unfixed_error);
+ }
+ return;
+ }
+
+ if (!Editor.commit(commit)) {
+ ++NumFailures;
+ Diag(Info.getLocation(), diag::note_fixit_failed);
+ return;
+ }
+
+ Diag(Info.getLocation(), diag::note_fixit_applied);
+}
+
+/// \brief Emit a diagnostic via the adapted diagnostic client.
+void FixItRewriter::Diag(SourceLocation Loc, unsigned DiagID) {
+ // When producing this diagnostic, we temporarily bypass ourselves,
+ // clear out any current diagnostic, and let the downstream client
+ // format the diagnostic.
+ Diags.takeClient();
+ Diags.setClient(Client);
+ Diags.Clear();
+ Diags.Report(Loc, DiagID);
+ Diags.takeClient();
+ Diags.setClient(this);
+}
+
+DiagnosticConsumer *FixItRewriter::clone(DiagnosticsEngine &Diags) const {
+ return new FixItRewriter(Diags, Diags.getSourceManager(),
+ Rewrite.getLangOpts(), FixItOpts);
+}
+
+FixItOptions::~FixItOptions() {}
diff --git a/lib/Rewrite/Frontend/FrontendActions.cpp b/lib/Rewrite/Frontend/FrontendActions.cpp
new file mode 100644
index 0000000000..7d29b6d421
--- /dev/null
+++ b/lib/Rewrite/Frontend/FrontendActions.cpp
@@ -0,0 +1,192 @@
+//===--- FrontendActions.cpp ----------------------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Rewrite/Frontend/FrontendActions.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Parse/Parser.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendDiagnostic.h"
+#include "clang/Frontend/Utils.h"
+#include "clang/Rewrite/Frontend/ASTConsumers.h"
+#include "clang/Rewrite/Frontend/FixItRewriter.h"
+#include "clang/Rewrite/Frontend/Rewriters.h"
+#include "llvm/ADT/OwningPtr.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/FileSystem.h"
+
+using namespace clang;
+
+//===----------------------------------------------------------------------===//
+// AST Consumer Actions
+//===----------------------------------------------------------------------===//
+
+ASTConsumer *HTMLPrintAction::CreateASTConsumer(CompilerInstance &CI,
+ StringRef InFile) {
+ if (raw_ostream *OS = CI.createDefaultOutputFile(false, InFile))
+ return CreateHTMLPrinter(OS, CI.getPreprocessor());
+ return 0;
+}
+
+FixItAction::FixItAction() {}
+FixItAction::~FixItAction() {}
+
+ASTConsumer *FixItAction::CreateASTConsumer(CompilerInstance &CI,
+ StringRef InFile) {
+ return new ASTConsumer();
+}
+
+namespace {
+class FixItRewriteInPlace : public FixItOptions {
+public:
+ std::string RewriteFilename(const std::string &Filename, int &fd) {
+ fd = -1;
+ return Filename;
+ }
+};
+
+class FixItActionSuffixInserter : public FixItOptions {
+ std::string NewSuffix;
+
+public:
+ FixItActionSuffixInserter(std::string NewSuffix, bool FixWhatYouCan)
+ : NewSuffix(NewSuffix) {
+ this->FixWhatYouCan = FixWhatYouCan;
+ }
+
+ std::string RewriteFilename(const std::string &Filename, int &fd) {
+ fd = -1;
+ SmallString<128> Path(Filename);
+ llvm::sys::path::replace_extension(Path,
+ NewSuffix + llvm::sys::path::extension(Path));
+ return Path.str();
+ }
+};
+
+class FixItRewriteToTemp : public FixItOptions {
+public:
+ std::string RewriteFilename(const std::string &Filename, int &fd) {
+ SmallString<128> Path;
+ Path = llvm::sys::path::filename(Filename);
+ Path += "-%%%%%%%%";
+ Path += llvm::sys::path::extension(Filename);
+ SmallString<128> NewPath;
+ llvm::sys::fs::unique_file(Path.str(), fd, NewPath);
+ return NewPath.str();
+ }
+};
+} // end anonymous namespace
+
+bool FixItAction::BeginSourceFileAction(CompilerInstance &CI,
+ StringRef Filename) {
+ const FrontendOptions &FEOpts = getCompilerInstance().getFrontendOpts();
+ if (!FEOpts.FixItSuffix.empty()) {
+ FixItOpts.reset(new FixItActionSuffixInserter(FEOpts.FixItSuffix,
+ FEOpts.FixWhatYouCan));
+ } else {
+ FixItOpts.reset(new FixItRewriteInPlace);
+ FixItOpts->FixWhatYouCan = FEOpts.FixWhatYouCan;
+ }
+ Rewriter.reset(new FixItRewriter(CI.getDiagnostics(), CI.getSourceManager(),
+ CI.getLangOpts(), FixItOpts.get()));
+ return true;
+}
+
+void FixItAction::EndSourceFileAction() {
+ // Otherwise rewrite all files.
+ Rewriter->WriteFixedFiles();
+}
+
+bool FixItRecompile::BeginInvocation(CompilerInstance &CI) {
+
+ std::vector<std::pair<std::string, std::string> > RewrittenFiles;
+ bool err = false;
+ {
+ const FrontendOptions &FEOpts = CI.getFrontendOpts();
+ OwningPtr<FrontendAction> FixAction(new SyntaxOnlyAction());
+ if (FixAction->BeginSourceFile(CI, FEOpts.Inputs[0])) {
+ OwningPtr<FixItOptions> FixItOpts;
+ if (FEOpts.FixToTemporaries)
+ FixItOpts.reset(new FixItRewriteToTemp());
+ else
+ FixItOpts.reset(new FixItRewriteInPlace());
+ FixItOpts->Silent = true;
+ FixItOpts->FixWhatYouCan = FEOpts.FixWhatYouCan;
+ FixItOpts->FixOnlyWarnings = FEOpts.FixOnlyWarnings;
+ FixItRewriter Rewriter(CI.getDiagnostics(), CI.getSourceManager(),
+ CI.getLangOpts(), FixItOpts.get());
+ FixAction->Execute();
+
+ err = Rewriter.WriteFixedFiles(&RewrittenFiles);
+
+ FixAction->EndSourceFile();
+ CI.setSourceManager(0);
+ CI.setFileManager(0);
+ } else {
+ err = true;
+ }
+ }
+ if (err)
+ return false;
+ CI.getDiagnosticClient().clear();
+ CI.getDiagnostics().Reset();
+
+ PreprocessorOptions &PPOpts = CI.getPreprocessorOpts();
+ PPOpts.RemappedFiles.insert(PPOpts.RemappedFiles.end(),
+ RewrittenFiles.begin(), RewrittenFiles.end());
+ PPOpts.RemappedFilesKeepOriginalName = false;
+
+ return true;
+}
+
+//===----------------------------------------------------------------------===//
+// Preprocessor Actions
+//===----------------------------------------------------------------------===//
+
+ASTConsumer *RewriteObjCAction::CreateASTConsumer(CompilerInstance &CI,
+ StringRef InFile) {
+ if (raw_ostream *OS = CI.createDefaultOutputFile(false, InFile, "cpp")) {
+ if (CI.getLangOpts().ObjCRuntime.isNonFragile())
+ return CreateModernObjCRewriter(InFile, OS,
+ CI.getDiagnostics(), CI.getLangOpts(),
+ CI.getDiagnosticOpts().NoRewriteMacros);
+ return CreateObjCRewriter(InFile, OS,
+ CI.getDiagnostics(), CI.getLangOpts(),
+ CI.getDiagnosticOpts().NoRewriteMacros);
+ }
+ return 0;
+}
+
+void RewriteMacrosAction::ExecuteAction() {
+ CompilerInstance &CI = getCompilerInstance();
+ raw_ostream *OS = CI.createDefaultOutputFile(true, getCurrentFile());
+ if (!OS) return;
+
+ RewriteMacrosInInput(CI.getPreprocessor(), OS);
+}
+
+void RewriteTestAction::ExecuteAction() {
+ CompilerInstance &CI = getCompilerInstance();
+ raw_ostream *OS = CI.createDefaultOutputFile(false, getCurrentFile());
+ if (!OS) return;
+
+ DoRewriteTest(CI.getPreprocessor(), OS);
+}
+
+void RewriteIncludesAction::ExecuteAction() {
+ CompilerInstance &CI = getCompilerInstance();
+ raw_ostream *OS = CI.createDefaultOutputFile(true, getCurrentFile());
+ if (!OS) return;
+
+ RewriteIncludesInInput(CI.getPreprocessor(), OS,
+ CI.getPreprocessorOutputOpts());
+}
diff --git a/lib/Rewrite/Frontend/HTMLPrint.cpp b/lib/Rewrite/Frontend/HTMLPrint.cpp
new file mode 100644
index 0000000000..79e44470ad
--- /dev/null
+++ b/lib/Rewrite/Frontend/HTMLPrint.cpp
@@ -0,0 +1,94 @@
+//===--- HTMLPrint.cpp - Source code -> HTML pretty-printing --------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Pretty-printing of source code to HTML.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Rewrite/Frontend/ASTConsumers.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Rewrite/Core/HTMLRewrite.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/raw_ostream.h"
+using namespace clang;
+
+//===----------------------------------------------------------------------===//
+// Functional HTML pretty-printing.
+//===----------------------------------------------------------------------===//
+
+namespace {
+ class HTMLPrinter : public ASTConsumer {
+ Rewriter R;
+ raw_ostream *Out;
+ Preprocessor &PP;
+ bool SyntaxHighlight, HighlightMacros;
+
+ public:
+ HTMLPrinter(raw_ostream *OS, Preprocessor &pp,
+ bool _SyntaxHighlight, bool _HighlightMacros)
+ : Out(OS), PP(pp), SyntaxHighlight(_SyntaxHighlight),
+ HighlightMacros(_HighlightMacros) {}
+
+ void Initialize(ASTContext &context);
+ void HandleTranslationUnit(ASTContext &Ctx);
+ };
+}
+
+ASTConsumer* clang::CreateHTMLPrinter(raw_ostream *OS,
+ Preprocessor &PP,
+ bool SyntaxHighlight,
+ bool HighlightMacros) {
+ return new HTMLPrinter(OS, PP, SyntaxHighlight, HighlightMacros);
+}
+
+void HTMLPrinter::Initialize(ASTContext &context) {
+ R.setSourceMgr(context.getSourceManager(), context.getLangOpts());
+}
+
+void HTMLPrinter::HandleTranslationUnit(ASTContext &Ctx) {
+ if (PP.getDiagnostics().hasErrorOccurred())
+ return;
+
+ // Format the file.
+ FileID FID = R.getSourceMgr().getMainFileID();
+ const FileEntry* Entry = R.getSourceMgr().getFileEntryForID(FID);
+ const char* Name;
+ // In some cases, in particular the case where the input is from stdin,
+ // there is no entry. Fall back to the memory buffer for a name in those
+ // cases.
+ if (Entry)
+ Name = Entry->getName();
+ else
+ Name = R.getSourceMgr().getBuffer(FID)->getBufferIdentifier();
+
+ html::AddLineNumbers(R, FID);
+ html::AddHeaderFooterInternalBuiltinCSS(R, FID, Name);
+
+ // If we have a preprocessor, relex the file and syntax highlight.
+ // We might not have a preprocessor if we come from a deserialized AST file,
+ // for example.
+
+ if (SyntaxHighlight) html::SyntaxHighlight(R, FID, PP);
+ if (HighlightMacros) html::HighlightMacros(R, FID, PP);
+ html::EscapeText(R, FID, false, true);
+
+ // Emit the HTML.
+ const RewriteBuffer &RewriteBuf = R.getEditBuffer(FID);
+ char *Buffer = (char*)malloc(RewriteBuf.size());
+ std::copy(RewriteBuf.begin(), RewriteBuf.end(), Buffer);
+ Out->write(Buffer, RewriteBuf.size());
+ free(Buffer);
+}
diff --git a/lib/Rewrite/Frontend/InclusionRewriter.cpp b/lib/Rewrite/Frontend/InclusionRewriter.cpp
new file mode 100644
index 0000000000..1929d72123
--- /dev/null
+++ b/lib/Rewrite/Frontend/InclusionRewriter.cpp
@@ -0,0 +1,361 @@
+//===--- InclusionRewriter.cpp - Rewrite includes into their expansions ---===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This code rewrites include invocations into their expansions. This gives you
+// a file with all included files merged into it.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Rewrite/Frontend/Rewriters.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Frontend/PreprocessorOutputOptions.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace llvm;
+
+namespace {
+
+class InclusionRewriter : public PPCallbacks {
+ /// Information about which #includes were actually performed,
+ /// created by preprocessor callbacks.
+ struct FileChange {
+ SourceLocation From;
+ FileID Id;
+ SrcMgr::CharacteristicKind FileType;
+ FileChange(SourceLocation From) : From(From) {
+ }
+ };
+ Preprocessor &PP; ///< Used to find inclusion directives.
+ SourceManager &SM; ///< Used to read and manage source files.
+ raw_ostream &OS; ///< The destination stream for rewritten contents.
+ bool ShowLineMarkers; ///< Show #line markers.
+ bool UseLineDirective; ///< Use of line directives or line markers.
+ typedef std::map<unsigned, FileChange> FileChangeMap;
+ FileChangeMap FileChanges; /// Tracks which files were included where.
+ /// Used transitively for building up the FileChanges mapping over the
+ /// various \c PPCallbacks callbacks.
+ FileChangeMap::iterator LastInsertedFileChange;
+public:
+ InclusionRewriter(Preprocessor &PP, raw_ostream &OS, bool ShowLineMarkers);
+ bool Process(FileID FileId, SrcMgr::CharacteristicKind FileType);
+private:
+ virtual void FileChanged(SourceLocation Loc, FileChangeReason Reason,
+ SrcMgr::CharacteristicKind FileType,
+ FileID PrevFID);
+ virtual void FileSkipped(const FileEntry &ParentFile,
+ const Token &FilenameTok,
+ SrcMgr::CharacteristicKind FileType);
+ virtual void InclusionDirective(SourceLocation HashLoc,
+ const Token &IncludeTok,
+ StringRef FileName,
+ bool IsAngled,
+ const FileEntry *File,
+ SourceLocation EndLoc,
+ StringRef SearchPath,
+ StringRef RelativePath);
+ void WriteLineInfo(const char *Filename, int Line,
+ SrcMgr::CharacteristicKind FileType,
+ StringRef EOL, StringRef Extra = StringRef());
+ void OutputContentUpTo(const MemoryBuffer &FromFile,
+ unsigned &WriteFrom, unsigned WriteTo,
+ StringRef EOL, int &lines,
+ bool EnsureNewline = false);
+ void CommentOutDirective(Lexer &DirectivesLex, const Token &StartToken,
+ const MemoryBuffer &FromFile, StringRef EOL,
+ unsigned &NextToWrite, int &Lines);
+ const FileChange *FindFileChangeLocation(SourceLocation Loc) const;
+ StringRef NextIdentifierName(Lexer &RawLex, Token &RawToken);
+};
+
+} // end anonymous namespace
+
+/// Initializes an InclusionRewriter with a \p PP source and \p OS destination.
+InclusionRewriter::InclusionRewriter(Preprocessor &PP, raw_ostream &OS,
+ bool ShowLineMarkers)
+ : PP(PP), SM(PP.getSourceManager()), OS(OS),
+ ShowLineMarkers(ShowLineMarkers),
+ LastInsertedFileChange(FileChanges.end()) {
+ // If we're in microsoft mode, use normal #line instead of line markers.
+ UseLineDirective = PP.getLangOpts().MicrosoftExt;
+}
+
+/// Write appropriate line information as either #line directives or GNU line
+/// markers depending on what mode we're in, including the \p Filename and
+/// \p Line we are located at, using the specified \p EOL line separator, and
+/// any \p Extra context specifiers in GNU line directives.
+void InclusionRewriter::WriteLineInfo(const char *Filename, int Line,
+ SrcMgr::CharacteristicKind FileType,
+ StringRef EOL, StringRef Extra) {
+ if (!ShowLineMarkers)
+ return;
+ if (UseLineDirective) {
+ OS << "#line" << ' ' << Line << ' ' << '"' << Filename << '"';
+ } else {
+ // Use GNU linemarkers as described here:
+ // http://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html
+ OS << '#' << ' ' << Line << ' ' << '"' << Filename << '"';
+ if (!Extra.empty())
+ OS << Extra;
+ if (FileType == SrcMgr::C_System)
+ // "`3' This indicates that the following text comes from a system header
+ // file, so certain warnings should be suppressed."
+ OS << " 3";
+ else if (FileType == SrcMgr::C_ExternCSystem)
+ // as above for `3', plus "`4' This indicates that the following text
+ // should be treated as being wrapped in an implicit extern "C" block."
+ OS << " 3 4";
+ }
+ OS << EOL;
+}
+
+/// FileChanged - Whenever the preprocessor enters or exits a #include file
+/// it invokes this handler.
+void InclusionRewriter::FileChanged(SourceLocation Loc,
+ FileChangeReason Reason,
+ SrcMgr::CharacteristicKind NewFileType,
+ FileID) {
+ if (Reason != EnterFile)
+ return;
+ if (LastInsertedFileChange == FileChanges.end())
+ // we didn't reach this file (eg: the main file) via an inclusion directive
+ return;
+ LastInsertedFileChange->second.Id = FullSourceLoc(Loc, SM).getFileID();
+ LastInsertedFileChange->second.FileType = NewFileType;
+ LastInsertedFileChange = FileChanges.end();
+}
+
+/// Called whenever an inclusion is skipped due to canonical header protection
+/// macros.
+void InclusionRewriter::FileSkipped(const FileEntry &/*ParentFile*/,
+ const Token &/*FilenameTok*/,
+ SrcMgr::CharacteristicKind /*FileType*/) {
+ assert(LastInsertedFileChange != FileChanges.end() && "A file, that wasn't "
+ "found via an inclusion directive, was skipped");
+ FileChanges.erase(LastInsertedFileChange);
+ LastInsertedFileChange = FileChanges.end();
+}
+
+/// This should be called whenever the preprocessor encounters include
+/// directives. It does not say whether the file has been included, but it
+/// provides more information about the directive (hash location instead
+/// of location inside the included file). It is assumed that the matching
+/// FileChanged() or FileSkipped() is called after this.
+void InclusionRewriter::InclusionDirective(SourceLocation HashLoc,
+ const Token &/*IncludeTok*/,
+ StringRef /*FileName*/,
+ bool /*IsAngled*/,
+ const FileEntry * /*File*/,
+ SourceLocation /*EndLoc*/,
+ StringRef /*SearchPath*/,
+ StringRef /*RelativePath*/) {
+ assert(LastInsertedFileChange == FileChanges.end() && "Another inclusion "
+ "directive was found before the previous one was processed");
+ std::pair<FileChangeMap::iterator, bool> p = FileChanges.insert(
+ std::make_pair(HashLoc.getRawEncoding(), FileChange(HashLoc)));
+ assert(p.second && "Unexpected revisitation of the same include directive");
+ LastInsertedFileChange = p.first;
+}
+
+/// Simple lookup for a SourceLocation (specifically one denoting the hash in
+/// an inclusion directive) in the map of inclusion information, FileChanges.
+const InclusionRewriter::FileChange *
+InclusionRewriter::FindFileChangeLocation(SourceLocation Loc) const {
+ FileChangeMap::const_iterator I = FileChanges.find(Loc.getRawEncoding());
+ if (I != FileChanges.end())
+ return &I->second;
+ return NULL;
+}
+
+/// Detect the likely line ending style of \p FromFile by examining the first
+/// newline found within it.
+static StringRef DetectEOL(const MemoryBuffer &FromFile) {
+ // detect what line endings the file uses, so that added content does not mix
+ // the style
+ const char *Pos = strchr(FromFile.getBufferStart(), '\n');
+ if (Pos == NULL)
+ return "\n";
+ if (Pos + 1 < FromFile.getBufferEnd() && Pos[1] == '\r')
+ return "\n\r";
+ if (Pos - 1 >= FromFile.getBufferStart() && Pos[-1] == '\r')
+ return "\r\n";
+ return "\n";
+}
+
+/// Writes out bytes from \p FromFile, starting at \p NextToWrite and ending at
+/// \p WriteTo - 1.
+void InclusionRewriter::OutputContentUpTo(const MemoryBuffer &FromFile,
+ unsigned &WriteFrom, unsigned WriteTo,
+ StringRef EOL, int &Line,
+ bool EnsureNewline) {
+ if (WriteTo <= WriteFrom)
+ return;
+ OS.write(FromFile.getBufferStart() + WriteFrom, WriteTo - WriteFrom);
+ // count lines manually, it's faster than getPresumedLoc()
+ Line += std::count(FromFile.getBufferStart() + WriteFrom,
+ FromFile.getBufferStart() + WriteTo, '\n');
+ if (EnsureNewline) {
+ char LastChar = FromFile.getBufferStart()[WriteTo - 1];
+ if (LastChar != '\n' && LastChar != '\r')
+ OS << EOL;
+ }
+ WriteFrom = WriteTo;
+}
+
+/// Print characters from \p FromFile starting at \p NextToWrite up until the
+/// inclusion directive at \p StartToken, then print out the inclusion
+/// inclusion directive disabled by a #if directive, updating \p NextToWrite
+/// and \p Line to track the number of source lines visited and the progress
+/// through the \p FromFile buffer.
+void InclusionRewriter::CommentOutDirective(Lexer &DirectiveLex,
+ const Token &StartToken,
+ const MemoryBuffer &FromFile,
+ StringRef EOL,
+ unsigned &NextToWrite, int &Line) {
+ OutputContentUpTo(FromFile, NextToWrite,
+ SM.getFileOffset(StartToken.getLocation()), EOL, Line);
+ Token DirectiveToken;
+ do {
+ DirectiveLex.LexFromRawLexer(DirectiveToken);
+ } while (!DirectiveToken.is(tok::eod) && DirectiveToken.isNot(tok::eof));
+ OS << "#if 0 /* expanded by -frewrite-includes */" << EOL;
+ OutputContentUpTo(FromFile, NextToWrite,
+ SM.getFileOffset(DirectiveToken.getLocation()) + DirectiveToken.getLength(),
+ EOL, Line);
+ OS << "#endif /* expanded by -frewrite-includes */" << EOL;
+}
+
+/// Find the next identifier in the pragma directive specified by \p RawToken.
+StringRef InclusionRewriter::NextIdentifierName(Lexer &RawLex,
+ Token &RawToken) {
+ RawLex.LexFromRawLexer(RawToken);
+ if (RawToken.is(tok::raw_identifier))
+ PP.LookUpIdentifierInfo(RawToken);
+ if (RawToken.is(tok::identifier))
+ return RawToken.getIdentifierInfo()->getName();
+ return StringRef();
+}
+
+/// Use a raw lexer to analyze \p FileId, inccrementally copying parts of it
+/// and including content of included files recursively.
+bool InclusionRewriter::Process(FileID FileId,
+ SrcMgr::CharacteristicKind FileType)
+{
+ bool Invalid;
+ const MemoryBuffer &FromFile = *SM.getBuffer(FileId, &Invalid);
+ if (Invalid) // invalid inclusion
+ return true;
+ const char *FileName = FromFile.getBufferIdentifier();
+ Lexer RawLex(FileId, &FromFile, PP.getSourceManager(), PP.getLangOpts());
+ RawLex.SetCommentRetentionState(false);
+
+ StringRef EOL = DetectEOL(FromFile);
+
+ // Per the GNU docs: "1" indicates the start of a new file.
+ WriteLineInfo(FileName, 1, FileType, EOL, " 1");
+
+ if (SM.getFileIDSize(FileId) == 0)
+ return true;
+
+ // The next byte to be copied from the source file
+ unsigned NextToWrite = 0;
+ int Line = 1; // The current input file line number.
+
+ Token RawToken;
+ RawLex.LexFromRawLexer(RawToken);
+
+ // TODO: Consider adding a switch that strips possibly unimportant content,
+ // such as comments, to reduce the size of repro files.
+ while (RawToken.isNot(tok::eof)) {
+ if (RawToken.is(tok::hash) && RawToken.isAtStartOfLine()) {
+ RawLex.setParsingPreprocessorDirective(true);
+ Token HashToken = RawToken;
+ RawLex.LexFromRawLexer(RawToken);
+ if (RawToken.is(tok::raw_identifier))
+ PP.LookUpIdentifierInfo(RawToken);
+ if (RawToken.is(tok::identifier)) {
+ switch (RawToken.getIdentifierInfo()->getPPKeywordID()) {
+ case tok::pp_include:
+ case tok::pp_include_next:
+ case tok::pp_import: {
+ CommentOutDirective(RawLex, HashToken, FromFile, EOL, NextToWrite,
+ Line);
+ if (const FileChange *Change = FindFileChangeLocation(
+ HashToken.getLocation())) {
+ // now include and recursively process the file
+ if (Process(Change->Id, Change->FileType))
+ // and set lineinfo back to this file, if the nested one was
+ // actually included
+ // `2' indicates returning to a file (after having included
+ // another file.
+ WriteLineInfo(FileName, Line, FileType, EOL, " 2");
+ } else
+ // fix up lineinfo (since commented out directive changed line
+ // numbers) for inclusions that were skipped due to header guards
+ WriteLineInfo(FileName, Line, FileType, EOL);
+ break;
+ }
+ case tok::pp_pragma: {
+ StringRef Identifier = NextIdentifierName(RawLex, RawToken);
+ if (Identifier == "clang" || Identifier == "GCC") {
+ if (NextIdentifierName(RawLex, RawToken) == "system_header") {
+ // keep the directive in, commented out
+ CommentOutDirective(RawLex, HashToken, FromFile, EOL,
+ NextToWrite, Line);
+ // update our own type
+ FileType = SM.getFileCharacteristic(RawToken.getLocation());
+ WriteLineInfo(FileName, Line, FileType, EOL);
+ }
+ } else if (Identifier == "once") {
+ // keep the directive in, commented out
+ CommentOutDirective(RawLex, HashToken, FromFile, EOL,
+ NextToWrite, Line);
+ WriteLineInfo(FileName, Line, FileType, EOL);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ RawLex.setParsingPreprocessorDirective(false);
+ }
+ RawLex.LexFromRawLexer(RawToken);
+ }
+ OutputContentUpTo(FromFile, NextToWrite,
+ SM.getFileOffset(SM.getLocForEndOfFile(FileId)) + 1, EOL, Line,
+ /*EnsureNewline*/true);
+ return true;
+}
+
+/// InclusionRewriterInInput - Implement -frewrite-includes mode.
+void clang::RewriteIncludesInInput(Preprocessor &PP, raw_ostream *OS,
+ const PreprocessorOutputOptions &Opts) {
+ SourceManager &SM = PP.getSourceManager();
+ InclusionRewriter *Rewrite = new InclusionRewriter(PP, *OS,
+ Opts.ShowLineMarkers);
+ PP.addPPCallbacks(Rewrite);
+
+ // First let the preprocessor process the entire file and call callbacks.
+ // Callbacks will record which #include's were actually performed.
+ PP.EnterMainSourceFile();
+ Token Tok;
+ // Only preprocessor directives matter here, so disable macro expansion
+ // everywhere else as an optimization.
+ // TODO: It would be even faster if the preprocessor could be switched
+ // to a mode where it would parse only preprocessor directives and comments,
+ // nothing else matters for parsing or processing.
+ PP.SetMacroExpansionOnlyInDirectives();
+ do {
+ PP.Lex(Tok);
+ } while (Tok.isNot(tok::eof));
+ Rewrite->Process(SM.getMainFileID(), SrcMgr::C_User);
+ OS->flush();
+}
diff --git a/lib/Rewrite/Frontend/Ma