John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 1 | //===--- tools/pp-trace/PPTrace.cpp - Clang preprocessor tracer -----------===// |
| 2 | // |
| 3 | // The LLVM Compiler Infrastructure |
| 4 | // |
| 5 | // This file is distributed under the University of Illinois Open Source |
| 6 | // License. See LICENSE.TXT for details. |
| 7 | // |
| 8 | //===----------------------------------------------------------------------===// |
| 9 | // |
| 10 | // This file implements pp-trace, a tool for displaying a textual trace |
| 11 | // of the Clang preprocessor activity. It's based on a derivation of the |
| 12 | // PPCallbacks class, that once registerd with Clang, receives callback calls |
| 13 | // to its virtual members, and outputs the information passed to the callbacks |
| 14 | // in a high-level YAML format. |
| 15 | // |
| 16 | // The pp-trace tool also serves as the basis for a test of the PPCallbacks |
| 17 | // mechanism. |
| 18 | // |
| 19 | // The pp-trace tool supports the following general command line format: |
| 20 | // |
John Thompson | 17c4f7d | 2013-10-31 12:53:42 +0000 | [diff] [blame] | 21 | // pp-trace [pp-trace options] (source file) [compiler options] |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 22 | // |
| 23 | // Basically you put the pp-trace options first, then the source file or files, |
| 24 | // and then any options you want to pass to the compiler. |
| 25 | // |
| 26 | // These are the pp-trace options: |
| 27 | // |
| 28 | // -ignore (callback list) Don't display output for a comma-separated |
| 29 | // list of callbacks, i.e.: |
| 30 | // -ignore "FileChanged,InclusionDirective" |
| 31 | // |
| 32 | // -output (file) Output trace to the given file in a YAML |
| 33 | // format, e.g.: |
| 34 | // |
| 35 | // --- |
| 36 | // - Callback: Name |
| 37 | // Argument1: Value1 |
| 38 | // Argument2: Value2 |
| 39 | // (etc.) |
| 40 | // ... |
| 41 | // |
| 42 | // Future Directions: |
| 43 | // |
| 44 | // 1. Add option opposite to "-ignore" that specifys a comma-separated option |
| 45 | // list of callbacs. Perhaps "-only" or "-exclusive". |
| 46 | // |
| 47 | //===----------------------------------------------------------------------===// |
| 48 | |
| 49 | #include "PPCallbacksTracker.h" |
| 50 | #include "clang/AST/ASTConsumer.h" |
| 51 | #include "clang/AST/ASTContext.h" |
| 52 | #include "clang/AST/RecursiveASTVisitor.h" |
| 53 | #include "clang/Basic/SourceManager.h" |
| 54 | #include "clang/Driver/Options.h" |
| 55 | #include "clang/Frontend/CompilerInstance.h" |
| 56 | #include "clang/Frontend/FrontendActions.h" |
| 57 | #include "clang/Lex/Preprocessor.h" |
| 58 | #include "clang/Tooling/CompilationDatabase.h" |
| 59 | #include "clang/Tooling/Tooling.h" |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 60 | #include "llvm/Option/Arg.h" |
| 61 | #include "llvm/Option/ArgList.h" |
| 62 | #include "llvm/Option/OptTable.h" |
| 63 | #include "llvm/Option/Option.h" |
| 64 | #include "llvm/Support/CommandLine.h" |
| 65 | #include "llvm/Support/FileSystem.h" |
| 66 | #include "llvm/Support/MemoryBuffer.h" |
| 67 | #include "llvm/Support/Path.h" |
| 68 | #include "llvm/Support/ToolOutputFile.h" |
| 69 | #include <algorithm> |
| 70 | #include <fstream> |
| 71 | #include <iterator> |
| 72 | #include <string> |
| 73 | #include <vector> |
| 74 | |
| 75 | using namespace clang; |
| 76 | using namespace clang::driver; |
| 77 | using namespace clang::driver::options; |
| 78 | using namespace clang::tooling; |
| 79 | using namespace llvm; |
| 80 | using namespace llvm::opt; |
| 81 | |
Adrian Prantl | b97f5c1 | 2015-02-25 02:46:37 +0000 | [diff] [blame] | 82 | // Options: |
| 83 | |
| 84 | // Collect the source files. |
Benjamin Kramer | e710371 | 2015-03-23 12:49:15 +0000 | [diff] [blame^] | 85 | static cl::list<std::string> SourcePaths(cl::Positional, |
| 86 | cl::desc("<source0> [... <sourceN>]"), |
| 87 | cl::OneOrMore); |
Adrian Prantl | b97f5c1 | 2015-02-25 02:46:37 +0000 | [diff] [blame] | 88 | |
| 89 | // Option to specify a list or one or more callback names to ignore. |
Benjamin Kramer | e710371 | 2015-03-23 12:49:15 +0000 | [diff] [blame^] | 90 | static cl::opt<std::string> IgnoreCallbacks( |
Adrian Prantl | b97f5c1 | 2015-02-25 02:46:37 +0000 | [diff] [blame] | 91 | "ignore", cl::init(""), |
| 92 | cl::desc("Ignore callbacks, i.e. \"Callback1, Callback2...\".")); |
| 93 | |
| 94 | // Option to specify the trace output file name. |
Benjamin Kramer | e710371 | 2015-03-23 12:49:15 +0000 | [diff] [blame^] | 95 | static cl::opt<std::string> OutputFileName( |
Adrian Prantl | b97f5c1 | 2015-02-25 02:46:37 +0000 | [diff] [blame] | 96 | "output", cl::init(""), |
| 97 | cl::desc("Output trace to the given file name or '-' for stdout.")); |
| 98 | |
| 99 | // Collect all other arguments, which will be passed to the front end. |
Benjamin Kramer | e710371 | 2015-03-23 12:49:15 +0000 | [diff] [blame^] | 100 | static cl::list<std::string> |
| 101 | CC1Arguments(cl::ConsumeAfter, |
| 102 | cl::desc("<arguments to be passed to front end>...")); |
Adrian Prantl | b97f5c1 | 2015-02-25 02:46:37 +0000 | [diff] [blame] | 103 | |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 104 | // Frontend action stuff: |
| 105 | |
Benjamin Kramer | e710371 | 2015-03-23 12:49:15 +0000 | [diff] [blame^] | 106 | namespace { |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 107 | // Consumer is responsible for setting up the callbacks. |
| 108 | class PPTraceConsumer : public ASTConsumer { |
| 109 | public: |
| 110 | PPTraceConsumer(SmallSet<std::string, 4> &Ignore, |
| 111 | std::vector<CallbackCall> &CallbackCalls, Preprocessor &PP) { |
| 112 | // PP takes ownership. |
Craig Topper | 775862a | 2014-09-10 05:07:57 +0000 | [diff] [blame] | 113 | PP.addPPCallbacks(llvm::make_unique<PPCallbacksTracker>(Ignore, |
| 114 | CallbackCalls, PP)); |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 115 | } |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 116 | }; |
| 117 | |
| 118 | class PPTraceAction : public SyntaxOnlyAction { |
| 119 | public: |
| 120 | PPTraceAction(SmallSet<std::string, 4> &Ignore, |
| 121 | std::vector<CallbackCall> &CallbackCalls) |
| 122 | : Ignore(Ignore), CallbackCalls(CallbackCalls) {} |
| 123 | |
| 124 | protected: |
David Blaikie | 680c4c8 | 2014-08-10 19:56:59 +0000 | [diff] [blame] | 125 | std::unique_ptr<clang::ASTConsumer> |
| 126 | CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { |
| 127 | return llvm::make_unique<PPTraceConsumer>(Ignore, CallbackCalls, |
| 128 | CI.getPreprocessor()); |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 129 | } |
| 130 | |
| 131 | private: |
| 132 | SmallSet<std::string, 4> &Ignore; |
| 133 | std::vector<CallbackCall> &CallbackCalls; |
| 134 | }; |
| 135 | |
| 136 | class PPTraceFrontendActionFactory : public FrontendActionFactory { |
| 137 | public: |
| 138 | PPTraceFrontendActionFactory(SmallSet<std::string, 4> &Ignore, |
| 139 | std::vector<CallbackCall> &CallbackCalls) |
| 140 | : Ignore(Ignore), CallbackCalls(CallbackCalls) {} |
| 141 | |
| 142 | virtual PPTraceAction *create() { |
| 143 | return new PPTraceAction(Ignore, CallbackCalls); |
| 144 | } |
| 145 | |
| 146 | private: |
| 147 | SmallSet<std::string, 4> &Ignore; |
| 148 | std::vector<CallbackCall> &CallbackCalls; |
| 149 | }; |
Benjamin Kramer | e710371 | 2015-03-23 12:49:15 +0000 | [diff] [blame^] | 150 | } // namespace |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 151 | |
| 152 | // Output the trace given its data structure and a stream. |
Benjamin Kramer | e710371 | 2015-03-23 12:49:15 +0000 | [diff] [blame^] | 153 | static int outputPPTrace(std::vector<CallbackCall> &CallbackCalls, |
| 154 | llvm::raw_ostream &OS) { |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 155 | // Mark start of document. |
| 156 | OS << "---\n"; |
| 157 | |
| 158 | for (std::vector<CallbackCall>::const_iterator I = CallbackCalls.begin(), |
| 159 | E = CallbackCalls.end(); |
| 160 | I != E; ++I) { |
| 161 | const CallbackCall &Callback = *I; |
| 162 | OS << "- Callback: " << Callback.Name << "\n"; |
| 163 | |
| 164 | for (std::vector<Argument>::const_iterator AI = Callback.Arguments.begin(), |
| 165 | AE = Callback.Arguments.end(); |
| 166 | AI != AE; ++AI) { |
| 167 | const Argument &Arg = *AI; |
| 168 | OS << " " << Arg.Name << ": " << Arg.Value << "\n"; |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | // Mark end of document. |
| 173 | OS << "...\n"; |
| 174 | |
| 175 | return 0; |
| 176 | } |
| 177 | |
| 178 | // Program entry point. |
| 179 | int main(int Argc, const char **Argv) { |
| 180 | |
| 181 | // Parse command line. |
| 182 | cl::ParseCommandLineOptions(Argc, Argv, "pp-trace.\n"); |
| 183 | |
| 184 | // Parse the IgnoreCallbacks list into strings. |
| 185 | SmallVector<StringRef, 32> IgnoreCallbacksStrings; |
| 186 | StringRef(IgnoreCallbacks).split(IgnoreCallbacksStrings, ",", |
| 187 | /*MaxSplit=*/ -1, /*KeepEmpty=*/false); |
| 188 | SmallSet<std::string, 4> Ignore; |
| 189 | for (SmallVector<StringRef, 32>::iterator I = IgnoreCallbacksStrings.begin(), |
| 190 | E = IgnoreCallbacksStrings.end(); |
| 191 | I != E; ++I) |
| 192 | Ignore.insert(*I); |
| 193 | |
| 194 | // Create the compilation database. |
| 195 | SmallString<256> PathBuf; |
| 196 | sys::fs::current_path(PathBuf); |
Ahmed Charles | 6a2dc5c | 2014-03-09 09:24:40 +0000 | [diff] [blame] | 197 | std::unique_ptr<CompilationDatabase> Compilations; |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 198 | Compilations.reset( |
| 199 | new FixedCompilationDatabase(Twine(PathBuf), CC1Arguments)); |
| 200 | |
| 201 | // Store the callback trace information here. |
| 202 | std::vector<CallbackCall> CallbackCalls; |
| 203 | |
| 204 | // Create the tool and run the compilation. |
| 205 | ClangTool Tool(*Compilations, SourcePaths); |
Benjamin Kramer | 6e91424 | 2014-07-24 10:23:33 +0000 | [diff] [blame] | 206 | PPTraceFrontendActionFactory Factory(Ignore, CallbackCalls); |
| 207 | int HadErrors = Tool.run(&Factory); |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 208 | |
| 209 | // If we had errors, exit early. |
| 210 | if (HadErrors) |
| 211 | return HadErrors; |
| 212 | |
| 213 | // Do the output. |
| 214 | if (!OutputFileName.size()) { |
| 215 | HadErrors = outputPPTrace(CallbackCalls, llvm::outs()); |
| 216 | } else { |
| 217 | // Set up output file. |
Rafael Espindola | b14bd53 | 2014-08-25 18:17:00 +0000 | [diff] [blame] | 218 | std::error_code EC; |
| 219 | llvm::tool_output_file Out(OutputFileName, EC, llvm::sys::fs::F_Text); |
| 220 | if (EC) { |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 221 | llvm::errs() << "pp-trace: error creating " << OutputFileName << ":" |
Rafael Espindola | b14bd53 | 2014-08-25 18:17:00 +0000 | [diff] [blame] | 222 | << EC.message() << "\n"; |
John Thompson | 9979454 | 2013-10-31 12:23:32 +0000 | [diff] [blame] | 223 | return 1; |
| 224 | } |
| 225 | |
| 226 | HadErrors = outputPPTrace(CallbackCalls, Out.os()); |
| 227 | |
| 228 | // Tell tool_output_file that we want to keep the file. |
| 229 | if (HadErrors == 0) |
| 230 | Out.keep(); |
| 231 | } |
| 232 | |
| 233 | return HadErrors; |
| 234 | } |