Home | History | Annotate | Line # | Download | only in Frontend
      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/raw_ostream.h"
     16 using namespace clang;
     17 
     18 namespace {
     19 class HeaderIncludesCallback : public PPCallbacks {
     20   SourceManager &SM;
     21   raw_ostream *OutputFile;
     22   const DependencyOutputOptions &DepOpts;
     23   unsigned CurrentIncludeDepth;
     24   bool HasProcessedPredefines;
     25   bool OwnsOutputFile;
     26   bool ShowAllHeaders;
     27   bool ShowDepth;
     28   bool MSStyle;
     29 
     30 public:
     31   HeaderIncludesCallback(const Preprocessor *PP, bool ShowAllHeaders_,
     32                          raw_ostream *OutputFile_,
     33                          const DependencyOutputOptions &DepOpts,
     34                          bool OwnsOutputFile_, bool ShowDepth_, bool MSStyle_)
     35       : SM(PP->getSourceManager()), OutputFile(OutputFile_), DepOpts(DepOpts),
     36         CurrentIncludeDepth(0), HasProcessedPredefines(false),
     37         OwnsOutputFile(OwnsOutputFile_), ShowAllHeaders(ShowAllHeaders_),
     38         ShowDepth(ShowDepth_), MSStyle(MSStyle_) {}
     39 
     40   ~HeaderIncludesCallback() override {
     41     if (OwnsOutputFile)
     42       delete OutputFile;
     43   }
     44 
     45   void FileChanged(SourceLocation Loc, FileChangeReason Reason,
     46                    SrcMgr::CharacteristicKind FileType,
     47                    FileID PrevFID) override;
     48 
     49   void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
     50                    SrcMgr::CharacteristicKind FileType) override;
     51 };
     52 }
     53 
     54 static void PrintHeaderInfo(raw_ostream *OutputFile, StringRef Filename,
     55                             bool ShowDepth, unsigned CurrentIncludeDepth,
     56                             bool MSStyle) {
     57   // Write to a temporary string to avoid unnecessary flushing on errs().
     58   SmallString<512> Pathname(Filename);
     59   if (!MSStyle)
     60     Lexer::Stringify(Pathname);
     61 
     62   SmallString<256> Msg;
     63   if (MSStyle)
     64     Msg += "Note: including file:";
     65 
     66   if (ShowDepth) {
     67     // The main source file is at depth 1, so skip one dot.
     68     for (unsigned i = 1; i != CurrentIncludeDepth; ++i)
     69       Msg += MSStyle ? ' ' : '.';
     70 
     71     if (!MSStyle)
     72       Msg += ' ';
     73   }
     74   Msg += Pathname;
     75   Msg += '\n';
     76 
     77   *OutputFile << Msg;
     78   OutputFile->flush();
     79 }
     80 
     81 void clang::AttachHeaderIncludeGen(Preprocessor &PP,
     82                                    const DependencyOutputOptions &DepOpts,
     83                                    bool ShowAllHeaders, StringRef OutputPath,
     84                                    bool ShowDepth, bool MSStyle) {
     85   raw_ostream *OutputFile = &llvm::errs();
     86   bool OwnsOutputFile = false;
     87 
     88   // Choose output stream, when printing in cl.exe /showIncludes style.
     89   if (MSStyle) {
     90     switch (DepOpts.ShowIncludesDest) {
     91     default:
     92       llvm_unreachable("Invalid destination for /showIncludes output!");
     93     case ShowIncludesDestination::Stderr:
     94       OutputFile = &llvm::errs();
     95       break;
     96     case ShowIncludesDestination::Stdout:
     97       OutputFile = &llvm::outs();
     98       break;
     99     }
    100   }
    101 
    102   // Open the output file, if used.
    103   if (!OutputPath.empty()) {
    104     std::error_code EC;
    105     llvm::raw_fd_ostream *OS = new llvm::raw_fd_ostream(
    106         OutputPath.str(), EC,
    107         llvm::sys::fs::OF_Append | llvm::sys::fs::OF_TextWithCRLF);
    108     if (EC) {
    109       PP.getDiagnostics().Report(clang::diag::warn_fe_cc_print_header_failure)
    110           << EC.message();
    111       delete OS;
    112     } else {
    113       OS->SetUnbuffered();
    114       OutputFile = OS;
    115       OwnsOutputFile = true;
    116     }
    117   }
    118 
    119   // Print header info for extra headers, pretending they were discovered by
    120   // the regular preprocessor. The primary use case is to support proper
    121   // generation of Make / Ninja file dependencies for implicit includes, such
    122   // as sanitizer blacklists. It's only important for cl.exe compatibility,
    123   // the GNU way to generate rules is -M / -MM / -MD / -MMD.
    124   for (const auto &Header : DepOpts.ExtraDeps)
    125     PrintHeaderInfo(OutputFile, Header.first, ShowDepth, 2, MSStyle);
    126   PP.addPPCallbacks(std::make_unique<HeaderIncludesCallback>(
    127       &PP, ShowAllHeaders, OutputFile, DepOpts, OwnsOutputFile, ShowDepth,
    128       MSStyle));
    129 }
    130 
    131 void HeaderIncludesCallback::FileChanged(SourceLocation Loc,
    132                                          FileChangeReason Reason,
    133                                          SrcMgr::CharacteristicKind NewFileType,
    134                                          FileID PrevFID) {
    135   // Unless we are exiting a #include, make sure to skip ahead to the line the
    136   // #include directive was at.
    137   PresumedLoc UserLoc = SM.getPresumedLoc(Loc);
    138   if (UserLoc.isInvalid())
    139     return;
    140 
    141   // Adjust the current include depth.
    142   if (Reason == PPCallbacks::EnterFile) {
    143     ++CurrentIncludeDepth;
    144   } else if (Reason == PPCallbacks::ExitFile) {
    145     if (CurrentIncludeDepth)
    146       --CurrentIncludeDepth;
    147 
    148     // We track when we are done with the predefines by watching for the first
    149     // place where we drop back to a nesting depth of 1.
    150     if (CurrentIncludeDepth == 1 && !HasProcessedPredefines) {
    151       if (!DepOpts.ShowIncludesPretendHeader.empty()) {
    152         PrintHeaderInfo(OutputFile, DepOpts.ShowIncludesPretendHeader,
    153                         ShowDepth, 2, MSStyle);
    154       }
    155       HasProcessedPredefines = true;
    156     }
    157 
    158     return;
    159   } else
    160     return;
    161 
    162   // Show the header if we are (a) past the predefines, or (b) showing all
    163   // headers and in the predefines at a depth past the initial file and command
    164   // line buffers.
    165   bool ShowHeader = (HasProcessedPredefines ||
    166                      (ShowAllHeaders && CurrentIncludeDepth > 2));
    167   unsigned IncludeDepth = CurrentIncludeDepth;
    168   if (!HasProcessedPredefines)
    169     --IncludeDepth; // Ignore indent from <built-in>.
    170   else if (!DepOpts.ShowIncludesPretendHeader.empty())
    171     ++IncludeDepth; // Pretend inclusion by ShowIncludesPretendHeader.
    172 
    173   if (!DepOpts.IncludeSystemHeaders && isSystem(NewFileType))
    174     ShowHeader = false;
    175 
    176   // Dump the header include information we are past the predefines buffer or
    177   // are showing all headers and this isn't the magic implicit <command line>
    178   // header.
    179   // FIXME: Identify headers in a more robust way than comparing their name to
    180   // "<command line>" and "<built-in>" in a bunch of places.
    181   if (ShowHeader && Reason == PPCallbacks::EnterFile &&
    182       UserLoc.getFilename() != StringRef("<command line>")) {
    183     PrintHeaderInfo(OutputFile, UserLoc.getFilename(), ShowDepth, IncludeDepth,
    184                     MSStyle);
    185   }
    186 }
    187 
    188 void HeaderIncludesCallback::FileSkipped(const FileEntryRef &SkippedFile, const
    189                                          Token &FilenameTok,
    190                                          SrcMgr::CharacteristicKind FileType) {
    191   if (!DepOpts.ShowSkippedHeaderIncludes)
    192     return;
    193 
    194   if (!DepOpts.IncludeSystemHeaders && isSystem(FileType))
    195     return;
    196 
    197   PrintHeaderInfo(OutputFile, SkippedFile.getName(), ShowDepth,
    198                   CurrentIncludeDepth + 1, MSStyle);
    199 }
    200