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