1 //===-- HeaderIncludeGen.cpp - Generate Header Includes -------------------===// 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 #include "clang/Frontend/DependencyOutputOptions.h" 10 #include "clang/Frontend/Utils.h" 11 #include "clang/Basic/SourceManager.h" 12 #include "clang/Frontend/FrontendDiagnostic.h" 13 #include "clang/Lex/Preprocessor.h" 14 #include "llvm/ADT/SmallString.h" 15 #include "llvm/Support/JSON.h" 16 #include "llvm/Support/raw_ostream.h" 17 using namespace clang; 18 19 namespace { 20 class HeaderIncludesCallback : public PPCallbacks { 21 SourceManager &SM; 22 raw_ostream *OutputFile; 23 const DependencyOutputOptions &DepOpts; 24 unsigned CurrentIncludeDepth; 25 bool HasProcessedPredefines; 26 bool OwnsOutputFile; 27 bool ShowAllHeaders; 28 bool ShowDepth; 29 bool MSStyle; 30 31 public: 32 HeaderIncludesCallback(const Preprocessor *PP, bool ShowAllHeaders_, 33 raw_ostream *OutputFile_, 34 const DependencyOutputOptions &DepOpts, 35 bool OwnsOutputFile_, bool ShowDepth_, bool MSStyle_) 36 : SM(PP->getSourceManager()), OutputFile(OutputFile_), DepOpts(DepOpts), 37 CurrentIncludeDepth(0), HasProcessedPredefines(false), 38 OwnsOutputFile(OwnsOutputFile_), ShowAllHeaders(ShowAllHeaders_), 39 ShowDepth(ShowDepth_), MSStyle(MSStyle_) {} 40 41 ~HeaderIncludesCallback() override { 42 if (OwnsOutputFile) 43 delete OutputFile; 44 } 45 46 void FileChanged(SourceLocation Loc, FileChangeReason Reason, 47 SrcMgr::CharacteristicKind FileType, 48 FileID PrevFID) override; 49 50 void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok, 51 SrcMgr::CharacteristicKind FileType) override; 52 53 private: 54 bool ShouldShowHeader(SrcMgr::CharacteristicKind HeaderType) { 55 if (!DepOpts.IncludeSystemHeaders && isSystem(HeaderType)) 56 return false; 57 58 // Show the current header if we are (a) past the predefines, or (b) showing 59 // all headers and in the predefines at a depth past the initial file and 60 // command line buffers. 61 return (HasProcessedPredefines || 62 (ShowAllHeaders && CurrentIncludeDepth > 2)); 63 } 64 }; 65 66 /// A callback for emitting header usage information to a file in JSON. Each 67 /// line in the file is a JSON object that includes the source file name and 68 /// the list of headers directly or indirectly included from it. For example: 69 /// 70 /// {"source":"/tmp/foo.c", 71 /// "includes":["/usr/include/stdio.h", "/usr/include/stdlib.h"]} 72 /// 73 /// To reduce the amount of data written to the file, we only record system 74 /// headers that are directly included from a file that isn't in the system 75 /// directory. 76 class HeaderIncludesJSONCallback : public PPCallbacks { 77 SourceManager &SM; 78 raw_ostream *OutputFile; 79 bool OwnsOutputFile; 80 SmallVector<std::string, 16> IncludedHeaders; 81 82 public: 83 HeaderIncludesJSONCallback(const Preprocessor *PP, raw_ostream *OutputFile_, 84 bool OwnsOutputFile_) 85 : SM(PP->getSourceManager()), OutputFile(OutputFile_), 86 OwnsOutputFile(OwnsOutputFile_) {} 87 88 ~HeaderIncludesJSONCallback() override { 89 if (OwnsOutputFile) 90 delete OutputFile; 91 } 92 93 void EndOfMainFile() override; 94 95 void FileChanged(SourceLocation Loc, FileChangeReason Reason, 96 SrcMgr::CharacteristicKind FileType, 97 FileID PrevFID) override; 98 99 void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok, 100 SrcMgr::CharacteristicKind FileType) override; 101 }; 102 } 103 104 static void PrintHeaderInfo(raw_ostream *OutputFile, StringRef Filename, 105 bool ShowDepth, unsigned CurrentIncludeDepth, 106 bool MSStyle) { 107 // Write to a temporary string to avoid unnecessary flushing on errs(). 108 SmallString<512> Pathname(Filename); 109 if (!MSStyle) 110 Lexer::Stringify(Pathname); 111 112 SmallString<256> Msg; 113 if (MSStyle) 114 Msg += "Note: including file:"; 115 116 if (ShowDepth) { 117 // The main source file is at depth 1, so skip one dot. 118 for (unsigned i = 1; i != CurrentIncludeDepth; ++i) 119 Msg += MSStyle ? ' ' : '.'; 120 121 if (!MSStyle) 122 Msg += ' '; 123 } 124 Msg += Pathname; 125 Msg += '\n'; 126 127 *OutputFile << Msg; 128 OutputFile->flush(); 129 } 130 131 void clang::AttachHeaderIncludeGen(Preprocessor &PP, 132 const DependencyOutputOptions &DepOpts, 133 bool ShowAllHeaders, StringRef OutputPath, 134 bool ShowDepth, bool MSStyle) { 135 raw_ostream *OutputFile = &llvm::errs(); 136 bool OwnsOutputFile = false; 137 138 // Choose output stream, when printing in cl.exe /showIncludes style. 139 if (MSStyle) { 140 switch (DepOpts.ShowIncludesDest) { 141 default: 142 llvm_unreachable("Invalid destination for /showIncludes output!"); 143 case ShowIncludesDestination::Stderr: 144 OutputFile = &llvm::errs(); 145 break; 146 case ShowIncludesDestination::Stdout: 147 OutputFile = &llvm::outs(); 148 break; 149 } 150 } 151 152 // Open the output file, if used. 153 if (!OutputPath.empty()) { 154 std::error_code EC; 155 llvm::raw_fd_ostream *OS = new llvm::raw_fd_ostream( 156 OutputPath.str(), EC, 157 llvm::sys::fs::OF_Append | llvm::sys::fs::OF_TextWithCRLF); 158 if (EC) { 159 PP.getDiagnostics().Report(clang::diag::warn_fe_cc_print_header_failure) 160 << EC.message(); 161 delete OS; 162 } else { 163 OS->SetUnbuffered(); 164 OutputFile = OS; 165 OwnsOutputFile = true; 166 } 167 } 168 169 switch (DepOpts.HeaderIncludeFormat) { 170 case HIFMT_None: 171 llvm_unreachable("unexpected header format kind"); 172 case HIFMT_Textual: { 173 assert(DepOpts.HeaderIncludeFiltering == HIFIL_None && 174 "header filtering is currently always disabled when output format is" 175 "textual"); 176 // Print header info for extra headers, pretending they were discovered by 177 // the regular preprocessor. The primary use case is to support proper 178 // generation of Make / Ninja file dependencies for implicit includes, such 179 // as sanitizer ignorelists. It's only important for cl.exe compatibility, 180 // the GNU way to generate rules is -M / -MM / -MD / -MMD. 181 for (const auto &Header : DepOpts.ExtraDeps) 182 PrintHeaderInfo(OutputFile, Header.first, ShowDepth, 2, MSStyle); 183 PP.addPPCallbacks(std::make_unique<HeaderIncludesCallback>( 184 &PP, ShowAllHeaders, OutputFile, DepOpts, OwnsOutputFile, ShowDepth, 185 MSStyle)); 186 break; 187 } 188 case HIFMT_JSON: { 189 assert(DepOpts.HeaderIncludeFiltering == HIFIL_Only_Direct_System && 190 "only-direct-system is the only option for filtering"); 191 PP.addPPCallbacks(std::make_unique<HeaderIncludesJSONCallback>( 192 &PP, OutputFile, OwnsOutputFile)); 193 break; 194 } 195 } 196 } 197 198 void HeaderIncludesCallback::FileChanged(SourceLocation Loc, 199 FileChangeReason Reason, 200 SrcMgr::CharacteristicKind NewFileType, 201 FileID PrevFID) { 202 // Unless we are exiting a #include, make sure to skip ahead to the line the 203 // #include directive was at. 204 PresumedLoc UserLoc = SM.getPresumedLoc(Loc); 205 if (UserLoc.isInvalid()) 206 return; 207 208 // Adjust the current include depth. 209 if (Reason == PPCallbacks::EnterFile) { 210 ++CurrentIncludeDepth; 211 } else if (Reason == PPCallbacks::ExitFile) { 212 if (CurrentIncludeDepth) 213 --CurrentIncludeDepth; 214 215 // We track when we are done with the predefines by watching for the first 216 // place where we drop back to a nesting depth of 1. 217 if (CurrentIncludeDepth == 1 && !HasProcessedPredefines) 218 HasProcessedPredefines = true; 219 220 return; 221 } else { 222 return; 223 } 224 225 if (!ShouldShowHeader(NewFileType)) 226 return; 227 228 unsigned IncludeDepth = CurrentIncludeDepth; 229 if (!HasProcessedPredefines) 230 --IncludeDepth; // Ignore indent from <built-in>. 231 232 // FIXME: Identify headers in a more robust way than comparing their name to 233 // "<command line>" and "<built-in>" in a bunch of places. 234 if (Reason == PPCallbacks::EnterFile && 235 UserLoc.getFilename() != StringRef("<command line>")) { 236 PrintHeaderInfo(OutputFile, UserLoc.getFilename(), ShowDepth, IncludeDepth, 237 MSStyle); 238 } 239 } 240 241 void HeaderIncludesCallback::FileSkipped(const FileEntryRef &SkippedFile, const 242 Token &FilenameTok, 243 SrcMgr::CharacteristicKind FileType) { 244 if (!DepOpts.ShowSkippedHeaderIncludes) 245 return; 246 247 if (!ShouldShowHeader(FileType)) 248 return; 249 250 PrintHeaderInfo(OutputFile, SkippedFile.getName(), ShowDepth, 251 CurrentIncludeDepth + 1, MSStyle); 252 } 253 254 void HeaderIncludesJSONCallback::EndOfMainFile() { 255 const FileEntry *FE = SM.getFileEntryForID(SM.getMainFileID()); 256 SmallString<256> MainFile(FE->getName()); 257 SM.getFileManager().makeAbsolutePath(MainFile); 258 259 std::string Str; 260 llvm::raw_string_ostream OS(Str); 261 llvm::json::OStream JOS(OS); 262 JOS.object([&] { 263 JOS.attribute("source", MainFile.c_str()); 264 JOS.attributeArray("includes", [&] { 265 llvm::StringSet<> SeenHeaders; 266 for (const std::string &H : IncludedHeaders) 267 if (SeenHeaders.insert(H).second) 268 JOS.value(H); 269 }); 270 }); 271 OS << "\n"; 272 273 if (OutputFile->get_kind() == raw_ostream::OStreamKind::OK_FDStream) { 274 llvm::raw_fd_ostream *FDS = static_cast<llvm::raw_fd_ostream *>(OutputFile); 275 if (auto L = FDS->lock()) 276 *OutputFile << Str; 277 } else 278 *OutputFile << Str; 279 } 280 281 /// Determine whether the header file should be recorded. The header file should 282 /// be recorded only if the header file is a system header and the current file 283 /// isn't a system header. 284 static bool shouldRecordNewFile(SrcMgr::CharacteristicKind NewFileType, 285 SourceLocation PrevLoc, SourceManager &SM) { 286 return SrcMgr::isSystem(NewFileType) && !SM.isInSystemHeader(PrevLoc); 287 } 288 289 void HeaderIncludesJSONCallback::FileChanged( 290 SourceLocation Loc, FileChangeReason Reason, 291 SrcMgr::CharacteristicKind NewFileType, FileID PrevFID) { 292 if (PrevFID.isInvalid() || 293 !shouldRecordNewFile(NewFileType, SM.getLocForStartOfFile(PrevFID), SM)) 294 return; 295 296 // Unless we are exiting a #include, make sure to skip ahead to the line the 297 // #include directive was at. 298 PresumedLoc UserLoc = SM.getPresumedLoc(Loc); 299 if (UserLoc.isInvalid()) 300 return; 301 302 if (Reason == PPCallbacks::EnterFile && 303 UserLoc.getFilename() != StringRef("<command line>")) 304 IncludedHeaders.push_back(UserLoc.getFilename()); 305 } 306 307 void HeaderIncludesJSONCallback::FileSkipped( 308 const FileEntryRef &SkippedFile, const Token &FilenameTok, 309 SrcMgr::CharacteristicKind FileType) { 310 if (!shouldRecordNewFile(FileType, FilenameTok.getLocation(), SM)) 311 return; 312 313 IncludedHeaders.push_back(SkippedFile.getName().str()); 314 } 315