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