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