10b57cec5SDimitry Andric //===-- HeaderIncludeGen.cpp - Generate Header Includes -------------------===// 20b57cec5SDimitry Andric // 30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60b57cec5SDimitry Andric // 70b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 80b57cec5SDimitry Andric 90b57cec5SDimitry Andric #include "clang/Frontend/DependencyOutputOptions.h" 100b57cec5SDimitry Andric #include "clang/Frontend/Utils.h" 110b57cec5SDimitry Andric #include "clang/Basic/SourceManager.h" 120b57cec5SDimitry Andric #include "clang/Frontend/FrontendDiagnostic.h" 130b57cec5SDimitry Andric #include "clang/Lex/Preprocessor.h" 140b57cec5SDimitry Andric #include "llvm/ADT/SmallString.h" 15bdd1243dSDimitry Andric #include "llvm/Support/JSON.h" 160b57cec5SDimitry Andric #include "llvm/Support/raw_ostream.h" 170b57cec5SDimitry Andric using namespace clang; 180b57cec5SDimitry Andric 190b57cec5SDimitry Andric namespace { 200b57cec5SDimitry Andric class HeaderIncludesCallback : public PPCallbacks { 210b57cec5SDimitry Andric SourceManager &SM; 220b57cec5SDimitry Andric raw_ostream *OutputFile; 230b57cec5SDimitry Andric const DependencyOutputOptions &DepOpts; 240b57cec5SDimitry Andric unsigned CurrentIncludeDepth; 250b57cec5SDimitry Andric bool HasProcessedPredefines; 260b57cec5SDimitry Andric bool OwnsOutputFile; 270b57cec5SDimitry Andric bool ShowAllHeaders; 280b57cec5SDimitry Andric bool ShowDepth; 290b57cec5SDimitry Andric bool MSStyle; 300b57cec5SDimitry Andric 310b57cec5SDimitry Andric public: 320b57cec5SDimitry Andric HeaderIncludesCallback(const Preprocessor *PP, bool ShowAllHeaders_, 330b57cec5SDimitry Andric raw_ostream *OutputFile_, 340b57cec5SDimitry Andric const DependencyOutputOptions &DepOpts, 350b57cec5SDimitry Andric bool OwnsOutputFile_, bool ShowDepth_, bool MSStyle_) 360b57cec5SDimitry Andric : SM(PP->getSourceManager()), OutputFile(OutputFile_), DepOpts(DepOpts), 370b57cec5SDimitry Andric CurrentIncludeDepth(0), HasProcessedPredefines(false), 380b57cec5SDimitry Andric OwnsOutputFile(OwnsOutputFile_), ShowAllHeaders(ShowAllHeaders_), 390b57cec5SDimitry Andric ShowDepth(ShowDepth_), MSStyle(MSStyle_) {} 400b57cec5SDimitry Andric 410b57cec5SDimitry Andric ~HeaderIncludesCallback() override { 420b57cec5SDimitry Andric if (OwnsOutputFile) 430b57cec5SDimitry Andric delete OutputFile; 440b57cec5SDimitry Andric } 450b57cec5SDimitry Andric 4606c3fb27SDimitry Andric HeaderIncludesCallback(const HeaderIncludesCallback &) = delete; 4706c3fb27SDimitry Andric HeaderIncludesCallback &operator=(const HeaderIncludesCallback &) = delete; 4806c3fb27SDimitry Andric 490b57cec5SDimitry Andric void FileChanged(SourceLocation Loc, FileChangeReason Reason, 500b57cec5SDimitry Andric SrcMgr::CharacteristicKind FileType, 510b57cec5SDimitry Andric FileID PrevFID) override; 52fe6060f1SDimitry Andric 53fe6060f1SDimitry Andric void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok, 54fe6060f1SDimitry Andric SrcMgr::CharacteristicKind FileType) override; 5506c3fb27SDimitry Andric 5606c3fb27SDimitry Andric private: 5706c3fb27SDimitry Andric bool ShouldShowHeader(SrcMgr::CharacteristicKind HeaderType) { 5806c3fb27SDimitry Andric if (!DepOpts.IncludeSystemHeaders && isSystem(HeaderType)) 5906c3fb27SDimitry Andric return false; 6006c3fb27SDimitry Andric 6106c3fb27SDimitry Andric // Show the current header if we are (a) past the predefines, or (b) showing 6206c3fb27SDimitry Andric // all headers and in the predefines at a depth past the initial file and 6306c3fb27SDimitry Andric // command line buffers. 6406c3fb27SDimitry Andric return (HasProcessedPredefines || 6506c3fb27SDimitry Andric (ShowAllHeaders && CurrentIncludeDepth > 2)); 6606c3fb27SDimitry Andric } 670b57cec5SDimitry Andric }; 68bdd1243dSDimitry Andric 69bdd1243dSDimitry Andric /// A callback for emitting header usage information to a file in JSON. Each 70bdd1243dSDimitry Andric /// line in the file is a JSON object that includes the source file name and 71bdd1243dSDimitry Andric /// the list of headers directly or indirectly included from it. For example: 72bdd1243dSDimitry Andric /// 73bdd1243dSDimitry Andric /// {"source":"/tmp/foo.c", 74bdd1243dSDimitry Andric /// "includes":["/usr/include/stdio.h", "/usr/include/stdlib.h"]} 75bdd1243dSDimitry Andric /// 76bdd1243dSDimitry Andric /// To reduce the amount of data written to the file, we only record system 77bdd1243dSDimitry Andric /// headers that are directly included from a file that isn't in the system 78bdd1243dSDimitry Andric /// directory. 79bdd1243dSDimitry Andric class HeaderIncludesJSONCallback : public PPCallbacks { 80bdd1243dSDimitry Andric SourceManager &SM; 81bdd1243dSDimitry Andric raw_ostream *OutputFile; 82bdd1243dSDimitry Andric bool OwnsOutputFile; 83bdd1243dSDimitry Andric SmallVector<std::string, 16> IncludedHeaders; 84bdd1243dSDimitry Andric 85bdd1243dSDimitry Andric public: 86bdd1243dSDimitry Andric HeaderIncludesJSONCallback(const Preprocessor *PP, raw_ostream *OutputFile_, 87bdd1243dSDimitry Andric bool OwnsOutputFile_) 88bdd1243dSDimitry Andric : SM(PP->getSourceManager()), OutputFile(OutputFile_), 89bdd1243dSDimitry Andric OwnsOutputFile(OwnsOutputFile_) {} 90bdd1243dSDimitry Andric 91bdd1243dSDimitry Andric ~HeaderIncludesJSONCallback() override { 92bdd1243dSDimitry Andric if (OwnsOutputFile) 93bdd1243dSDimitry Andric delete OutputFile; 94bdd1243dSDimitry Andric } 95bdd1243dSDimitry Andric 9606c3fb27SDimitry Andric HeaderIncludesJSONCallback(const HeaderIncludesJSONCallback &) = delete; 9706c3fb27SDimitry Andric HeaderIncludesJSONCallback & 9806c3fb27SDimitry Andric operator=(const HeaderIncludesJSONCallback &) = delete; 9906c3fb27SDimitry Andric 100bdd1243dSDimitry Andric void EndOfMainFile() override; 101bdd1243dSDimitry Andric 102bdd1243dSDimitry Andric void FileChanged(SourceLocation Loc, FileChangeReason Reason, 103bdd1243dSDimitry Andric SrcMgr::CharacteristicKind FileType, 104bdd1243dSDimitry Andric FileID PrevFID) override; 105bdd1243dSDimitry Andric 106bdd1243dSDimitry Andric void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok, 107bdd1243dSDimitry Andric SrcMgr::CharacteristicKind FileType) override; 108bdd1243dSDimitry Andric }; 1090b57cec5SDimitry Andric } 1100b57cec5SDimitry Andric 1110b57cec5SDimitry Andric static void PrintHeaderInfo(raw_ostream *OutputFile, StringRef Filename, 1120b57cec5SDimitry Andric bool ShowDepth, unsigned CurrentIncludeDepth, 1130b57cec5SDimitry Andric bool MSStyle) { 1140b57cec5SDimitry Andric // Write to a temporary string to avoid unnecessary flushing on errs(). 1150b57cec5SDimitry Andric SmallString<512> Pathname(Filename); 1160b57cec5SDimitry Andric if (!MSStyle) 1170b57cec5SDimitry Andric Lexer::Stringify(Pathname); 1180b57cec5SDimitry Andric 1190b57cec5SDimitry Andric SmallString<256> Msg; 1200b57cec5SDimitry Andric if (MSStyle) 1210b57cec5SDimitry Andric Msg += "Note: including file:"; 1220b57cec5SDimitry Andric 1230b57cec5SDimitry Andric if (ShowDepth) { 1240b57cec5SDimitry Andric // The main source file is at depth 1, so skip one dot. 1250b57cec5SDimitry Andric for (unsigned i = 1; i != CurrentIncludeDepth; ++i) 1260b57cec5SDimitry Andric Msg += MSStyle ? ' ' : '.'; 1270b57cec5SDimitry Andric 1280b57cec5SDimitry Andric if (!MSStyle) 1290b57cec5SDimitry Andric Msg += ' '; 1300b57cec5SDimitry Andric } 1310b57cec5SDimitry Andric Msg += Pathname; 1320b57cec5SDimitry Andric Msg += '\n'; 1330b57cec5SDimitry Andric 1340b57cec5SDimitry Andric *OutputFile << Msg; 1350b57cec5SDimitry Andric OutputFile->flush(); 1360b57cec5SDimitry Andric } 1370b57cec5SDimitry Andric 1380b57cec5SDimitry Andric void clang::AttachHeaderIncludeGen(Preprocessor &PP, 1390b57cec5SDimitry Andric const DependencyOutputOptions &DepOpts, 1400b57cec5SDimitry Andric bool ShowAllHeaders, StringRef OutputPath, 1410b57cec5SDimitry Andric bool ShowDepth, bool MSStyle) { 1420b57cec5SDimitry Andric raw_ostream *OutputFile = &llvm::errs(); 1430b57cec5SDimitry Andric bool OwnsOutputFile = false; 1440b57cec5SDimitry Andric 1450b57cec5SDimitry Andric // Choose output stream, when printing in cl.exe /showIncludes style. 1460b57cec5SDimitry Andric if (MSStyle) { 1470b57cec5SDimitry Andric switch (DepOpts.ShowIncludesDest) { 1480b57cec5SDimitry Andric default: 1490b57cec5SDimitry Andric llvm_unreachable("Invalid destination for /showIncludes output!"); 1500b57cec5SDimitry Andric case ShowIncludesDestination::Stderr: 1510b57cec5SDimitry Andric OutputFile = &llvm::errs(); 1520b57cec5SDimitry Andric break; 1530b57cec5SDimitry Andric case ShowIncludesDestination::Stdout: 1540b57cec5SDimitry Andric OutputFile = &llvm::outs(); 1550b57cec5SDimitry Andric break; 1560b57cec5SDimitry Andric } 1570b57cec5SDimitry Andric } 1580b57cec5SDimitry Andric 1590b57cec5SDimitry Andric // Open the output file, if used. 1600b57cec5SDimitry Andric if (!OutputPath.empty()) { 1610b57cec5SDimitry Andric std::error_code EC; 1620b57cec5SDimitry Andric llvm::raw_fd_ostream *OS = new llvm::raw_fd_ostream( 163a7dea167SDimitry Andric OutputPath.str(), EC, 164fe6060f1SDimitry Andric llvm::sys::fs::OF_Append | llvm::sys::fs::OF_TextWithCRLF); 1650b57cec5SDimitry Andric if (EC) { 1660b57cec5SDimitry Andric PP.getDiagnostics().Report(clang::diag::warn_fe_cc_print_header_failure) 1670b57cec5SDimitry Andric << EC.message(); 1680b57cec5SDimitry Andric delete OS; 1690b57cec5SDimitry Andric } else { 1700b57cec5SDimitry Andric OS->SetUnbuffered(); 1710b57cec5SDimitry Andric OutputFile = OS; 1720b57cec5SDimitry Andric OwnsOutputFile = true; 1730b57cec5SDimitry Andric } 1740b57cec5SDimitry Andric } 1750b57cec5SDimitry Andric 176bdd1243dSDimitry Andric switch (DepOpts.HeaderIncludeFormat) { 177bdd1243dSDimitry Andric case HIFMT_None: 178bdd1243dSDimitry Andric llvm_unreachable("unexpected header format kind"); 179bdd1243dSDimitry Andric case HIFMT_Textual: { 180bdd1243dSDimitry Andric assert(DepOpts.HeaderIncludeFiltering == HIFIL_None && 181bdd1243dSDimitry Andric "header filtering is currently always disabled when output format is" 182bdd1243dSDimitry Andric "textual"); 1830b57cec5SDimitry Andric // Print header info for extra headers, pretending they were discovered by 1840b57cec5SDimitry Andric // the regular preprocessor. The primary use case is to support proper 1850b57cec5SDimitry Andric // generation of Make / Ninja file dependencies for implicit includes, such 186349cc55cSDimitry Andric // as sanitizer ignorelists. It's only important for cl.exe compatibility, 1870b57cec5SDimitry Andric // the GNU way to generate rules is -M / -MM / -MD / -MMD. 1880b57cec5SDimitry Andric for (const auto &Header : DepOpts.ExtraDeps) 189fe6060f1SDimitry Andric PrintHeaderInfo(OutputFile, Header.first, ShowDepth, 2, MSStyle); 190a7dea167SDimitry Andric PP.addPPCallbacks(std::make_unique<HeaderIncludesCallback>( 1910b57cec5SDimitry Andric &PP, ShowAllHeaders, OutputFile, DepOpts, OwnsOutputFile, ShowDepth, 1920b57cec5SDimitry Andric MSStyle)); 193bdd1243dSDimitry Andric break; 194bdd1243dSDimitry Andric } 195bdd1243dSDimitry Andric case HIFMT_JSON: { 196bdd1243dSDimitry Andric assert(DepOpts.HeaderIncludeFiltering == HIFIL_Only_Direct_System && 197bdd1243dSDimitry Andric "only-direct-system is the only option for filtering"); 198bdd1243dSDimitry Andric PP.addPPCallbacks(std::make_unique<HeaderIncludesJSONCallback>( 199bdd1243dSDimitry Andric &PP, OutputFile, OwnsOutputFile)); 200bdd1243dSDimitry Andric break; 201bdd1243dSDimitry Andric } 202bdd1243dSDimitry Andric } 2030b57cec5SDimitry Andric } 2040b57cec5SDimitry Andric 2050b57cec5SDimitry Andric void HeaderIncludesCallback::FileChanged(SourceLocation Loc, 2060b57cec5SDimitry Andric FileChangeReason Reason, 2070b57cec5SDimitry Andric SrcMgr::CharacteristicKind NewFileType, 2080b57cec5SDimitry Andric FileID PrevFID) { 2090b57cec5SDimitry Andric // Unless we are exiting a #include, make sure to skip ahead to the line the 2100b57cec5SDimitry Andric // #include directive was at. 2110b57cec5SDimitry Andric PresumedLoc UserLoc = SM.getPresumedLoc(Loc); 2120b57cec5SDimitry Andric if (UserLoc.isInvalid()) 2130b57cec5SDimitry Andric return; 2140b57cec5SDimitry Andric 2150b57cec5SDimitry Andric // Adjust the current include depth. 2160b57cec5SDimitry Andric if (Reason == PPCallbacks::EnterFile) { 2170b57cec5SDimitry Andric ++CurrentIncludeDepth; 2180b57cec5SDimitry Andric } else if (Reason == PPCallbacks::ExitFile) { 2190b57cec5SDimitry Andric if (CurrentIncludeDepth) 2200b57cec5SDimitry Andric --CurrentIncludeDepth; 2210b57cec5SDimitry Andric 2220b57cec5SDimitry Andric // We track when we are done with the predefines by watching for the first 2230b57cec5SDimitry Andric // place where we drop back to a nesting depth of 1. 22406c3fb27SDimitry Andric if (CurrentIncludeDepth == 1 && !HasProcessedPredefines) 2250b57cec5SDimitry Andric HasProcessedPredefines = true; 22606c3fb27SDimitry Andric 22706c3fb27SDimitry Andric return; 22806c3fb27SDimitry Andric } else { 22906c3fb27SDimitry Andric return; 2300b57cec5SDimitry Andric } 2310b57cec5SDimitry Andric 23206c3fb27SDimitry Andric if (!ShouldShowHeader(NewFileType)) 2330b57cec5SDimitry Andric return; 2340b57cec5SDimitry Andric 2350b57cec5SDimitry Andric unsigned IncludeDepth = CurrentIncludeDepth; 2360b57cec5SDimitry Andric if (!HasProcessedPredefines) 2370b57cec5SDimitry Andric --IncludeDepth; // Ignore indent from <built-in>. 2385ffd83dbSDimitry Andric 2390b57cec5SDimitry Andric // FIXME: Identify headers in a more robust way than comparing their name to 2400b57cec5SDimitry Andric // "<command line>" and "<built-in>" in a bunch of places. 24106c3fb27SDimitry Andric if (Reason == PPCallbacks::EnterFile && 2420b57cec5SDimitry Andric UserLoc.getFilename() != StringRef("<command line>")) { 2430b57cec5SDimitry Andric PrintHeaderInfo(OutputFile, UserLoc.getFilename(), ShowDepth, IncludeDepth, 2440b57cec5SDimitry Andric MSStyle); 2450b57cec5SDimitry Andric } 2460b57cec5SDimitry Andric } 247fe6060f1SDimitry Andric 248fe6060f1SDimitry Andric void HeaderIncludesCallback::FileSkipped(const FileEntryRef &SkippedFile, const 249fe6060f1SDimitry Andric Token &FilenameTok, 250fe6060f1SDimitry Andric SrcMgr::CharacteristicKind FileType) { 251fe6060f1SDimitry Andric if (!DepOpts.ShowSkippedHeaderIncludes) 252fe6060f1SDimitry Andric return; 253fe6060f1SDimitry Andric 25406c3fb27SDimitry Andric if (!ShouldShowHeader(FileType)) 255fe6060f1SDimitry Andric return; 256fe6060f1SDimitry Andric 257fe6060f1SDimitry Andric PrintHeaderInfo(OutputFile, SkippedFile.getName(), ShowDepth, 258fe6060f1SDimitry Andric CurrentIncludeDepth + 1, MSStyle); 259fe6060f1SDimitry Andric } 260bdd1243dSDimitry Andric 261bdd1243dSDimitry Andric void HeaderIncludesJSONCallback::EndOfMainFile() { 262*5f757f3fSDimitry Andric OptionalFileEntryRef FE = SM.getFileEntryRefForID(SM.getMainFileID()); 263bdd1243dSDimitry Andric SmallString<256> MainFile(FE->getName()); 264bdd1243dSDimitry Andric SM.getFileManager().makeAbsolutePath(MainFile); 265bdd1243dSDimitry Andric 266bdd1243dSDimitry Andric std::string Str; 267bdd1243dSDimitry Andric llvm::raw_string_ostream OS(Str); 268bdd1243dSDimitry Andric llvm::json::OStream JOS(OS); 269bdd1243dSDimitry Andric JOS.object([&] { 270bdd1243dSDimitry Andric JOS.attribute("source", MainFile.c_str()); 271bdd1243dSDimitry Andric JOS.attributeArray("includes", [&] { 272bdd1243dSDimitry Andric llvm::StringSet<> SeenHeaders; 273bdd1243dSDimitry Andric for (const std::string &H : IncludedHeaders) 274bdd1243dSDimitry Andric if (SeenHeaders.insert(H).second) 275bdd1243dSDimitry Andric JOS.value(H); 276bdd1243dSDimitry Andric }); 277bdd1243dSDimitry Andric }); 278bdd1243dSDimitry Andric OS << "\n"; 279bdd1243dSDimitry Andric 280bdd1243dSDimitry Andric if (OutputFile->get_kind() == raw_ostream::OStreamKind::OK_FDStream) { 281bdd1243dSDimitry Andric llvm::raw_fd_ostream *FDS = static_cast<llvm::raw_fd_ostream *>(OutputFile); 282bdd1243dSDimitry Andric if (auto L = FDS->lock()) 283bdd1243dSDimitry Andric *OutputFile << Str; 284bdd1243dSDimitry Andric } else 285bdd1243dSDimitry Andric *OutputFile << Str; 286bdd1243dSDimitry Andric } 287bdd1243dSDimitry Andric 288bdd1243dSDimitry Andric /// Determine whether the header file should be recorded. The header file should 289bdd1243dSDimitry Andric /// be recorded only if the header file is a system header and the current file 290bdd1243dSDimitry Andric /// isn't a system header. 291bdd1243dSDimitry Andric static bool shouldRecordNewFile(SrcMgr::CharacteristicKind NewFileType, 292bdd1243dSDimitry Andric SourceLocation PrevLoc, SourceManager &SM) { 293bdd1243dSDimitry Andric return SrcMgr::isSystem(NewFileType) && !SM.isInSystemHeader(PrevLoc); 294bdd1243dSDimitry Andric } 295bdd1243dSDimitry Andric 296bdd1243dSDimitry Andric void HeaderIncludesJSONCallback::FileChanged( 297bdd1243dSDimitry Andric SourceLocation Loc, FileChangeReason Reason, 298bdd1243dSDimitry Andric SrcMgr::CharacteristicKind NewFileType, FileID PrevFID) { 299bdd1243dSDimitry Andric if (PrevFID.isInvalid() || 300bdd1243dSDimitry Andric !shouldRecordNewFile(NewFileType, SM.getLocForStartOfFile(PrevFID), SM)) 301bdd1243dSDimitry Andric return; 302bdd1243dSDimitry Andric 303bdd1243dSDimitry Andric // Unless we are exiting a #include, make sure to skip ahead to the line the 304bdd1243dSDimitry Andric // #include directive was at. 305bdd1243dSDimitry Andric PresumedLoc UserLoc = SM.getPresumedLoc(Loc); 306bdd1243dSDimitry Andric if (UserLoc.isInvalid()) 307bdd1243dSDimitry Andric return; 308bdd1243dSDimitry Andric 309bdd1243dSDimitry Andric if (Reason == PPCallbacks::EnterFile && 310bdd1243dSDimitry Andric UserLoc.getFilename() != StringRef("<command line>")) 311bdd1243dSDimitry Andric IncludedHeaders.push_back(UserLoc.getFilename()); 312bdd1243dSDimitry Andric } 313bdd1243dSDimitry Andric 314bdd1243dSDimitry Andric void HeaderIncludesJSONCallback::FileSkipped( 315bdd1243dSDimitry Andric const FileEntryRef &SkippedFile, const Token &FilenameTok, 316bdd1243dSDimitry Andric SrcMgr::CharacteristicKind FileType) { 317bdd1243dSDimitry Andric if (!shouldRecordNewFile(FileType, FilenameTok.getLocation(), SM)) 318bdd1243dSDimitry Andric return; 319bdd1243dSDimitry Andric 320bdd1243dSDimitry Andric IncludedHeaders.push_back(SkippedFile.getName().str()); 321bdd1243dSDimitry Andric } 322