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