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 //
21 //    pp-trace [pp-trace options] (source file) [compiler options]
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"
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 
82 // Options:
83 
84 // Collect the source files.
85 cl::list<std::string> SourcePaths(cl::Positional,
86                                   cl::desc("<source0> [... <sourceN>]"),
87                                   cl::OneOrMore);
88 
89 // Option to specify a list or one or more callback names to ignore.
90 cl::opt<std::string> IgnoreCallbacks(
91     "ignore", cl::init(""),
92     cl::desc("Ignore callbacks, i.e. \"Callback1, Callback2...\"."));
93 
94 // Option to specify the trace output file name.
95 cl::opt<std::string> OutputFileName(
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.
100 cl::list<std::string>
101 CC1Arguments(cl::ConsumeAfter,
102              cl::desc("<arguments to be passed to front end>..."));
103 
104 // Frontend action stuff:
105 
106 // Consumer is responsible for setting up the callbacks.
107 class PPTraceConsumer : public ASTConsumer {
108 public:
109   PPTraceConsumer(SmallSet<std::string, 4> &Ignore,
110                   std::vector<CallbackCall> &CallbackCalls, Preprocessor &PP) {
111     // PP takes ownership.
112     PP.addPPCallbacks(new PPCallbacksTracker(Ignore, CallbackCalls, PP));
113   }
114 };
115 
116 class PPTraceAction : public SyntaxOnlyAction {
117 public:
118   PPTraceAction(SmallSet<std::string, 4> &Ignore,
119                 std::vector<CallbackCall> &CallbackCalls)
120       : Ignore(Ignore), CallbackCalls(CallbackCalls) {}
121 
122 protected:
123   virtual clang::ASTConsumer *CreateASTConsumer(CompilerInstance &CI,
124                                                 StringRef InFile) {
125     return new PPTraceConsumer(Ignore, CallbackCalls, CI.getPreprocessor());
126   }
127 
128 private:
129   SmallSet<std::string, 4> &Ignore;
130   std::vector<CallbackCall> &CallbackCalls;
131 };
132 
133 class PPTraceFrontendActionFactory : public FrontendActionFactory {
134 public:
135   PPTraceFrontendActionFactory(SmallSet<std::string, 4> &Ignore,
136                                std::vector<CallbackCall> &CallbackCalls)
137       : Ignore(Ignore), CallbackCalls(CallbackCalls) {}
138 
139   virtual PPTraceAction *create() {
140     return new PPTraceAction(Ignore, CallbackCalls);
141   }
142 
143 private:
144   SmallSet<std::string, 4> &Ignore;
145   std::vector<CallbackCall> &CallbackCalls;
146 };
147 
148 // Output the trace given its data structure and a stream.
149 int outputPPTrace(std::vector<CallbackCall> &CallbackCalls,
150                   llvm::raw_ostream &OS) {
151   // Mark start of document.
152   OS << "---\n";
153 
154   for (std::vector<CallbackCall>::const_iterator I = CallbackCalls.begin(),
155                                                  E = CallbackCalls.end();
156        I != E; ++I) {
157     const CallbackCall &Callback = *I;
158     OS << "- Callback: " << Callback.Name << "\n";
159 
160     for (std::vector<Argument>::const_iterator AI = Callback.Arguments.begin(),
161                                                AE = Callback.Arguments.end();
162          AI != AE; ++AI) {
163       const Argument &Arg = *AI;
164       OS << "  " << Arg.Name << ": " << Arg.Value << "\n";
165     }
166   }
167 
168   // Mark end of document.
169   OS << "...\n";
170 
171   return 0;
172 }
173 
174 // Program entry point.
175 int main(int Argc, const char **Argv) {
176 
177   // Parse command line.
178   cl::ParseCommandLineOptions(Argc, Argv, "pp-trace.\n");
179 
180   // Parse the IgnoreCallbacks list into strings.
181   SmallVector<StringRef, 32> IgnoreCallbacksStrings;
182   StringRef(IgnoreCallbacks).split(IgnoreCallbacksStrings, ",",
183                                    /*MaxSplit=*/ -1, /*KeepEmpty=*/false);
184   SmallSet<std::string, 4> Ignore;
185   for (SmallVector<StringRef, 32>::iterator I = IgnoreCallbacksStrings.begin(),
186                                             E = IgnoreCallbacksStrings.end();
187        I != E; ++I)
188     Ignore.insert(*I);
189 
190   // Create the compilation database.
191   SmallString<256> PathBuf;
192   sys::fs::current_path(PathBuf);
193   std::unique_ptr<CompilationDatabase> Compilations;
194   Compilations.reset(
195       new FixedCompilationDatabase(Twine(PathBuf), CC1Arguments));
196 
197   // Store the callback trace information here.
198   std::vector<CallbackCall> CallbackCalls;
199 
200   // Create the tool and run the compilation.
201   ClangTool Tool(*Compilations, SourcePaths);
202   int HadErrors =
203       Tool.run(new PPTraceFrontendActionFactory(Ignore, CallbackCalls));
204 
205   // If we had errors, exit early.
206   if (HadErrors)
207     return HadErrors;
208 
209   // Do the output.
210   if (!OutputFileName.size()) {
211     HadErrors = outputPPTrace(CallbackCalls, llvm::outs());
212   } else {
213     // Set up output file.
214     std::string Error;
215     llvm::tool_output_file Out(OutputFileName.c_str(), Error,
216                                llvm::sys::fs::F_Text);
217     if (!Error.empty()) {
218       llvm::errs() << "pp-trace: error creating " << OutputFileName << ":"
219                    << Error << "\n";
220       return 1;
221     }
222 
223     HadErrors = outputPPTrace(CallbackCalls, Out.os());
224 
225     // Tell tool_output_file that we want to keep the file.
226     if (HadErrors == 0)
227       Out.keep();
228   }
229 
230   return HadErrors;
231 }
232