diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h --- a/clang/include/clang/Driver/Driver.h +++ b/clang/include/clang/Driver/Driver.h @@ -233,9 +233,10 @@ /// CCPrintOptionsFilename or to stderr. unsigned CCPrintOptions : 1; - /// Set CC_PRINT_HEADERS mode, which causes the frontend to log header include - /// information to CCPrintHeadersFilename or to stderr. - unsigned CCPrintHeaders : 1; + /// The format of the header information that is emitted. If CC_PRINT_HEADERS + /// is set, the format is textual. Otherwise, the format is determined by the + /// enviroment variable CC_PRINT_HEADERS_FORMAT. + std::string CCPrintHeadersFormat; /// Set CC_LOG_DIAGNOSTICS mode, which causes the frontend to log diagnostics /// to CCLogDiagnosticsFilename or to stderr, in a stable machine readable diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -5656,6 +5656,11 @@ def header_include_file : Separate<["-"], "header-include-file">, HelpText<"Filename (or -) to write header include output to">, MarshallingInfoString>; +def header_include_format_EQ : Joined<["-"], "header-include-format=">, + HelpText<"set format in which header info is emitted">, + Values<"textual,json">, + NormalizedValuesScope<"DependencyOutputOptions">, NormalizedValues<["HIF_Textual", "HIF_JSON"]>, + MarshallingInfoEnum, "HIF_Textual">; def show_includes : Flag<["--"], "show-includes">, HelpText<"Print cl.exe style /showIncludes to stdout">; diff --git a/clang/include/clang/Frontend/DependencyOutputOptions.h b/clang/include/clang/Frontend/DependencyOutputOptions.h --- a/clang/include/clang/Frontend/DependencyOutputOptions.h +++ b/clang/include/clang/Frontend/DependencyOutputOptions.h @@ -44,6 +44,12 @@ /// due to the "include guard /// optimization" or #pragma once. + /// The format of header information. + enum HeaderIncludeFormatEnum { + HIF_Textual, + HIF_JSON + } HeaderIncludeFormat = HIF_Textual; + /// Destination of cl.exe style /showIncludes info. ShowIncludesDestination ShowIncludesDest = ShowIncludesDestination::None; @@ -80,7 +86,7 @@ DependencyOutputOptions() : IncludeSystemHeaders(0), ShowHeaderIncludes(0), UsePhonyTargets(0), AddMissingHeaderDeps(0), IncludeModuleFiles(0), - ShowSkippedHeaderIncludes(0) {} + ShowSkippedHeaderIncludes(0), HeaderIncludeFormat(HIF_Textual) {} }; } // end namespace clang diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -197,7 +197,7 @@ ModulesModeCXX20(false), LTOMode(LTOK_None), ClangExecutable(ClangExecutable), SysRoot(DEFAULT_SYSROOT), DriverTitle(Title), CCCPrintBindings(false), CCPrintOptions(false), - CCPrintHeaders(false), CCLogDiagnostics(false), CCGenDiagnostics(false), + CCLogDiagnostics(false), CCGenDiagnostics(false), CCPrintProcessStats(false), TargetTriple(TargetTriple), Saver(Alloc), CheckInputsExist(true), ProbePrecompiled(true), SuppressMissingInputWarning(false) { diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -5645,12 +5645,14 @@ } Args.AddAllArgs(CmdArgs, options::OPT_fshow_skipped_includes); - if (D.CCPrintHeaders && !D.CCGenDiagnostics) { + if (!D.CCPrintHeadersFormat.empty() && !D.CCGenDiagnostics) { CmdArgs.push_back("-header-include-file"); CmdArgs.push_back(!D.CCPrintHeadersFilename.empty() ? D.CCPrintHeadersFilename.c_str() : "-"); CmdArgs.push_back("-sys-header-deps"); + CmdArgs.push_back( + Args.MakeArgString("-header-include-format=" + D.CCPrintHeadersFormat)); } Args.AddLastArg(CmdArgs, options::OPT_P); Args.AddLastArg(CmdArgs, options::OPT_print_ivar_layout); diff --git a/clang/lib/Frontend/HeaderIncludeGen.cpp b/clang/lib/Frontend/HeaderIncludeGen.cpp --- a/clang/lib/Frontend/HeaderIncludeGen.cpp +++ b/clang/lib/Frontend/HeaderIncludeGen.cpp @@ -12,6 +12,7 @@ #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/SmallString.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -49,6 +50,43 @@ void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok, SrcMgr::CharacteristicKind FileType) override; }; + +/// A callback for emitting header usage information to a file in JSON. Each +/// line in the file is a JSON object that includes the source file name and +/// the list of headers directly or indirectly included from it. For example: +/// +/// {"source":"/tmp/foo.c", +/// "includes":["/usr/include/stdio.h", "/usr/include/stdlib.h"]} +/// +/// To reduce the amount of data written to the file, we only record system +/// headers that are directly included from a file that isn't in the system +/// directory. +class HeaderIncludesJSONCallback : public PPCallbacks { + SourceManager &SM; + raw_ostream *OutputFile; + bool OwnsOutputFile; + SmallVector IncludedHeaders; + +public: + HeaderIncludesJSONCallback(const Preprocessor *PP, raw_ostream *OutputFile_, + bool OwnsOutputFile_) + : SM(PP->getSourceManager()), OutputFile(OutputFile_), + OwnsOutputFile(OwnsOutputFile_) {} + + ~HeaderIncludesJSONCallback() override { + if (OwnsOutputFile) + delete OutputFile; + } + + void EndOfMainFile() override; + + void FileChanged(SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind FileType, + FileID PrevFID) override; + + void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok, + SrcMgr::CharacteristicKind FileType) override; +}; } static void PrintHeaderInfo(raw_ostream *OutputFile, StringRef Filename, @@ -116,16 +154,26 @@ } } - // Print header info for extra headers, pretending they were discovered by - // the regular preprocessor. The primary use case is to support proper - // generation of Make / Ninja file dependencies for implicit includes, such - // as sanitizer ignorelists. It's only important for cl.exe compatibility, - // the GNU way to generate rules is -M / -MM / -MD / -MMD. - for (const auto &Header : DepOpts.ExtraDeps) - PrintHeaderInfo(OutputFile, Header.first, ShowDepth, 2, MSStyle); - PP.addPPCallbacks(std::make_unique( - &PP, ShowAllHeaders, OutputFile, DepOpts, OwnsOutputFile, ShowDepth, - MSStyle)); + switch (DepOpts.HeaderIncludeFormat) { + case DependencyOutputOptions::HIF_Textual: { + // Print header info for extra headers, pretending they were discovered by + // the regular preprocessor. The primary use case is to support proper + // generation of Make / Ninja file dependencies for implicit includes, such + // as sanitizer ignorelists. It's only important for cl.exe compatibility, + // the GNU way to generate rules is -M / -MM / -MD / -MMD. + for (const auto &Header : DepOpts.ExtraDeps) + PrintHeaderInfo(OutputFile, Header.first, ShowDepth, 2, MSStyle); + PP.addPPCallbacks(std::make_unique( + &PP, ShowAllHeaders, OutputFile, DepOpts, OwnsOutputFile, ShowDepth, + MSStyle)); + break; + } + case DependencyOutputOptions::HIF_JSON: { + PP.addPPCallbacks(std::make_unique( + &PP, OutputFile, OwnsOutputFile)); + break; + } + } } void HeaderIncludesCallback::FileChanged(SourceLocation Loc, @@ -197,3 +245,70 @@ PrintHeaderInfo(OutputFile, SkippedFile.getName(), ShowDepth, CurrentIncludeDepth + 1, MSStyle); } + +void HeaderIncludesJSONCallback::EndOfMainFile() { + FileID MainID = SM.getMainFileID(); + std::string MainFilename; + + if (MainID.isValid()) + if (const FileEntry *FE = SM.getFileEntryForID(MainID)) + MainFilename = std::string(FE->getName()); + + SmallString<256> MainFile(MainFilename); + SM.getFileManager().makeAbsolutePath(MainFile); + + std::string Str; + llvm::raw_string_ostream OS(Str); + llvm::json::OStream JOS(OS); + JOS.object([&] { + JOS.attribute("source", MainFile.c_str()); + JOS.attributeArray("includes", [&] { + llvm::StringSet<> SeenHeaders; + for (const std::string &H : IncludedHeaders) + if (SeenHeaders.insert(H).second) + JOS.value(H); + }); + }); + OS << "\n"; + + if (OutputFile->get_kind() == raw_ostream::OStreamKind::OK_FDStream) { + llvm::raw_fd_ostream *FDS = static_cast(OutputFile); + if (auto L = FDS->lock()) + *OutputFile << Str; + } else + *OutputFile << Str; +} + +/// Determine whether the header file should be recorded. The header file should +/// be recorded only if the header file is a system header and the current file +/// isn't a system header. +static bool shouldRecordNewFile(SrcMgr::CharacteristicKind NewFileType, + SourceLocation PrevLoc, SourceManager &SM) { + return SrcMgr::isSystem(NewFileType) && !SM.isInSystemHeader(PrevLoc); +} + +void HeaderIncludesJSONCallback::FileChanged( + SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind NewFileType, FileID PrevFID) { + if (!shouldRecordNewFile(NewFileType, SM.getLocForStartOfFile(PrevFID), SM)) + return; + + // Unless we are exiting a #include, make sure to skip ahead to the line the + // #include directive was at. + PresumedLoc UserLoc = SM.getPresumedLoc(Loc); + if (UserLoc.isInvalid()) + return; + + if (Reason == PPCallbacks::EnterFile && + UserLoc.getFilename() != StringRef("")) + IncludedHeaders.push_back(UserLoc.getFilename()); +} + +void HeaderIncludesJSONCallback::FileSkipped( + const FileEntryRef &SkippedFile, const Token &FilenameTok, + SrcMgr::CharacteristicKind FileType) { + if (!shouldRecordNewFile(FileType, FilenameTok.getLocation(), SM)) + return; + + IncludedHeaders.push_back(SkippedFile.getName().str()); +} diff --git a/clang/test/Preprocessor/Inputs/print-header-json/header0.h b/clang/test/Preprocessor/Inputs/print-header-json/header0.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/print-header-json/header0.h @@ -0,0 +1,3 @@ +#include "system3.h" +#include "header1.h" +#include "header2.h" diff --git a/clang/test/Preprocessor/Inputs/print-header-json/header1.h b/clang/test/Preprocessor/Inputs/print-header-json/header1.h new file mode 100644 diff --git a/clang/test/Preprocessor/Inputs/print-header-json/header2.h b/clang/test/Preprocessor/Inputs/print-header-json/header2.h new file mode 100644 diff --git a/clang/test/Preprocessor/Inputs/print-header-json/system/system0.h b/clang/test/Preprocessor/Inputs/print-header-json/system/system0.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/print-header-json/system/system0.h @@ -0,0 +1,2 @@ +#include "system1.h" +#include "system2.h" diff --git a/clang/test/Preprocessor/Inputs/print-header-json/system/system1.h b/clang/test/Preprocessor/Inputs/print-header-json/system/system1.h new file mode 100644 diff --git a/clang/test/Preprocessor/Inputs/print-header-json/system/system2.h b/clang/test/Preprocessor/Inputs/print-header-json/system/system2.h new file mode 100644 diff --git a/clang/test/Preprocessor/Inputs/print-header-json/system/system3.h b/clang/test/Preprocessor/Inputs/print-header-json/system/system3.h new file mode 100644 diff --git a/clang/test/Preprocessor/print-header-json.c b/clang/test/Preprocessor/print-header-json.c new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/print-header-json.c @@ -0,0 +1,11 @@ +// RUN: %clang_cc1 -E -header-include-format=json -header-include-file %t.txt -I %S/Inputs/print-header-json -isystem %S/Inputs/print-header-json/system %s -o /dev/null +// RUN: cat %t.txt | FileCheck %s +// RUN: rm %t.txt +// RUN: env CC_PRINT_HEADERS_FORMAT=json CC_PRINT_HEADERS_FILE=%t.txt %clang -fsyntax-only -I %S/Inputs/print-header-json -isystem %S/Inputs/print-header-json/system %s -o /dev/null +// RUN: cat %t.txt | FileCheck %s + +#include "system0.h" +#include "header0.h" +#include "system2.h" + +// CHECK: {"source":"{{[^,]*}}/print-header-json.c","includes":["{{[^,]*}}/Inputs/print-header-json/system/system0.h","{{[^,]*}}/Inputs/print-header-json/system/system3.h","{{[^,]*}}/Inputs/print-header-json/system/system2.h"]} diff --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp --- a/clang/tools/driver/driver.cpp +++ b/clang/tools/driver/driver.cpp @@ -243,29 +243,34 @@ *NumberSignPtr = '='; } -static void SetBackdoorDriverOutputsFromEnvVars(Driver &TheDriver) { - auto CheckEnvVar = [](const char *EnvOptSet, const char *EnvOptFile, - std::string &OptFile) { - bool OptSet = !!::getenv(EnvOptSet); - if (OptSet) { - if (const char *Var = ::getenv(EnvOptFile)) - OptFile = Var; - } - return OptSet; - }; +template +static T checkEnvVar(const char *EnvOptSet, const char *EnvOptFile, + std::string &OptFile) { + T OptVal = ::getenv(EnvOptSet); + if (OptVal) { + if (const char *Var = ::getenv(EnvOptFile)) + OptFile = Var; + } + return OptVal; +} +static void SetBackdoorDriverOutputsFromEnvVars(Driver &TheDriver) { TheDriver.CCPrintOptions = - CheckEnvVar("CC_PRINT_OPTIONS", "CC_PRINT_OPTIONS_FILE", - TheDriver.CCPrintOptionsFilename); - TheDriver.CCPrintHeaders = - CheckEnvVar("CC_PRINT_HEADERS", "CC_PRINT_HEADERS_FILE", - TheDriver.CCPrintHeadersFilename); + checkEnvVar("CC_PRINT_OPTIONS", "CC_PRINT_OPTIONS_FILE", + TheDriver.CCPrintOptionsFilename); + if (checkEnvVar("CC_PRINT_HEADERS", "CC_PRINT_HEADERS_FILE", + TheDriver.CCPrintHeadersFilename)) + TheDriver.CCPrintHeadersFormat = "textual"; + else if (const char *EnvVar = checkEnvVar( + "CC_PRINT_HEADERS_FORMAT", "CC_PRINT_HEADERS_FILE", + TheDriver.CCPrintHeadersFilename)) + TheDriver.CCPrintHeadersFormat = EnvVar; TheDriver.CCLogDiagnostics = - CheckEnvVar("CC_LOG_DIAGNOSTICS", "CC_LOG_DIAGNOSTICS_FILE", - TheDriver.CCLogDiagnosticsFilename); + checkEnvVar("CC_LOG_DIAGNOSTICS", "CC_LOG_DIAGNOSTICS_FILE", + TheDriver.CCLogDiagnosticsFilename); TheDriver.CCPrintProcessStats = - CheckEnvVar("CC_PRINT_PROC_STAT", "CC_PRINT_PROC_STAT_FILE", - TheDriver.CCPrintStatReportFilename); + checkEnvVar("CC_PRINT_PROC_STAT", "CC_PRINT_PROC_STAT_FILE", + TheDriver.CCPrintStatReportFilename); } static void FixupDiagPrefixExeName(TextDiagnosticPrinter *DiagClient,