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 @@ -237,6 +237,8 @@ /// information to CCPrintHeadersFilename or to stderr. unsigned CCPrintHeaders : 1; + unsigned CCPrintHeadersJson : 1; + /// Set CC_LOG_DIAGNOSTICS mode, which causes the frontend to log diagnostics /// to CCLogDiagnosticsFilename or to stderr, in a stable machine readable /// format. 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,9 @@ def header_include_file : Separate<["-"], "header-include-file">, HelpText<"Filename (or -) to write header include output to">, MarshallingInfoString>; +def header_include_json : Flag<["-"], "header-include-json">, + HelpText<"Print header include info in json">, + MarshallingInfoFlag>; 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 @@ -34,6 +34,7 @@ public: unsigned IncludeSystemHeaders : 1; ///< Include system header dependencies. unsigned ShowHeaderIncludes : 1; ///< Show header inclusions (-H). + unsigned HeaderIncludeJSON : 1; ///< Print header include info in json. unsigned UsePhonyTargets : 1; ///< Include phony targets for each /// dependency, which can avoid some 'make' /// problems. @@ -78,8 +79,8 @@ public: DependencyOutputOptions() - : IncludeSystemHeaders(0), ShowHeaderIncludes(0), UsePhonyTargets(0), - AddMissingHeaderDeps(0), IncludeModuleFiles(0), + : IncludeSystemHeaders(0), ShowHeaderIncludes(0), HeaderIncludeJSON(0), + UsePhonyTargets(0), AddMissingHeaderDeps(0), IncludeModuleFiles(0), ShowSkippedHeaderIncludes(0) {} }; diff --git a/clang/include/clang/Frontend/Utils.h b/clang/include/clang/Frontend/Utils.h --- a/clang/include/clang/Frontend/Utils.h +++ b/clang/include/clang/Frontend/Utils.h @@ -180,8 +180,9 @@ void AttachHeaderIncludeGen(Preprocessor &PP, const DependencyOutputOptions &DepOpts, bool ShowAllHeaders = false, - StringRef OutputPath = {}, - bool ShowDepth = true, bool MSStyle = false); + StringRef OutputPath = {}, bool ShowDepth = true, + bool MSStyle = false, + bool HeaderIncludeJSON = false); /// The ChainedIncludesSource class converts headers to chained PCHs in /// memory, mainly for testing. 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,10 +197,10 @@ ModulesModeCXX20(false), LTOMode(LTOK_None), ClangExecutable(ClangExecutable), SysRoot(DEFAULT_SYSROOT), DriverTitle(Title), CCCPrintBindings(false), CCPrintOptions(false), - CCPrintHeaders(false), CCLogDiagnostics(false), CCGenDiagnostics(false), - CCPrintProcessStats(false), TargetTriple(TargetTriple), Saver(Alloc), - CheckInputsExist(true), ProbePrecompiled(true), - SuppressMissingInputWarning(false) { + CCPrintHeaders(false), CCPrintHeadersJson(false), CCLogDiagnostics(false), + CCGenDiagnostics(false), CCPrintProcessStats(false), + TargetTriple(TargetTriple), Saver(Alloc), CheckInputsExist(true), + ProbePrecompiled(true), SuppressMissingInputWarning(false) { // Provide a sane fallback if no VFS is specified. if (!this->VFS) this->VFS = llvm::vfs::getRealFileSystem(); 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.CCPrintHeaders || D.CCPrintHeadersJson) && !D.CCGenDiagnostics) { CmdArgs.push_back("-header-include-file"); CmdArgs.push_back(!D.CCPrintHeadersFilename.empty() ? D.CCPrintHeadersFilename.c_str() : "-"); CmdArgs.push_back("-sys-header-deps"); + if (D.CCPrintHeadersJson) + CmdArgs.push_back("-header-include-json"); } Args.AddLastArg(CmdArgs, options::OPT_P); Args.AddLastArg(CmdArgs, options::OPT_print_ivar_layout); diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -534,7 +534,8 @@ OutputPath = ""; AttachHeaderIncludeGen(*PP, DepOpts, /*ShowAllHeaders=*/true, OutputPath, - /*ShowDepth=*/false); + /*ShowDepth=*/false, /*MSStyle=*/false, + DepOpts.HeaderIncludeJSON); } if (DepOpts.ShowIncludesDest != ShowIncludesDestination::None) { 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,7 +12,10 @@ #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" +#include + using namespace clang; namespace { @@ -49,6 +52,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; + std::set 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, @@ -81,7 +121,8 @@ void clang::AttachHeaderIncludeGen(Preprocessor &PP, const DependencyOutputOptions &DepOpts, bool ShowAllHeaders, StringRef OutputPath, - bool ShowDepth, bool MSStyle) { + bool ShowDepth, bool MSStyle, + bool HeaderIncludeJSON) { raw_ostream *OutputFile = &llvm::errs(); bool OwnsOutputFile = false; @@ -116,6 +157,12 @@ } } + if (HeaderIncludeJSON) { + PP.addPPCallbacks(std::make_unique( + &PP, OutputFile, OwnsOutputFile)); + return; + } + // 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 @@ -197,3 +244,68 @@ 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", [&] { + for (const std::string &H : IncludedHeaders) + 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.insert(UserLoc.getFilename()); +} + +void HeaderIncludesJSONCallback::FileSkipped( + const FileEntryRef &SkippedFile, const Token &FilenameTok, + SrcMgr::CharacteristicKind FileType) { + if (!shouldRecordNewFile(FileType, FilenameTok.getLocation(), SM)) + return; + + IncludedHeaders.insert(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,8 @@ +// RUN: %clang_cc1 -E -header-include-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 + +#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/system2.h","{{[^,]*}}/Inputs/print-header-json/system/system3.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 @@ -260,6 +260,9 @@ TheDriver.CCPrintHeaders = CheckEnvVar("CC_PRINT_HEADERS", "CC_PRINT_HEADERS_FILE", TheDriver.CCPrintHeadersFilename); + TheDriver.CCPrintHeadersJson = + CheckEnvVar("CC_PRINT_HEADERS_JSON", "CC_PRINT_HEADERS_FILE", + TheDriver.CCPrintHeadersFilename); TheDriver.CCLogDiagnostics = CheckEnvVar("CC_LOG_DIAGNOSTICS", "CC_LOG_DIAGNOSTICS_FILE", TheDriver.CCLogDiagnosticsFilename);