[clang] 3738ce0 - Add support for a backdoor driver option that enables emitting header

Akira Hatanaka via cfe-commits cfe-commits at lists.llvm.org
Wed Dec 7 15:31:46 PST 2022


Author: Akira Hatanaka
Date: 2022-12-07T15:30:19-08:00
New Revision: 3738ce05a70cf97fc73a07b158a1726f61aed28b

URL: https://github.com/llvm/llvm-project/commit/3738ce05a70cf97fc73a07b158a1726f61aed28b
DIFF: https://github.com/llvm/llvm-project/commit/3738ce05a70cf97fc73a07b158a1726f61aed28b.diff

LOG: Add support for a backdoor driver option that enables emitting header
usage information in JSON to a file

Each line in the file is a JSON object that has the name of the main
source file followed by the list of system header files included
directly or indirectly from that file.

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, only the system
headers that are directly included from a non-system header file are
recorded.

In order to emit the header information in JSON, it is necessary to set
the following environment variables:

CC_PRINT_HEADERS_FORMAT=json CC_PRINT_HEADERS_FILTERING=only-direct-system

The following combination is equivalent to setting CC_PRINT_HEADERS=1:

CC_PRINT_HEADERS_FORMAT=textual CC_PRINT_HEADERS_FILTERING=none

Differential Revision: https://reviews.llvm.org/D137996

Added: 
    clang/include/clang/Basic/HeaderInclude.h
    clang/test/Preprocessor/Inputs/print-header-json/header0.h
    clang/test/Preprocessor/Inputs/print-header-json/header1.h
    clang/test/Preprocessor/Inputs/print-header-json/header2.h
    clang/test/Preprocessor/Inputs/print-header-json/system/system0.h
    clang/test/Preprocessor/Inputs/print-header-json/system/system1.h
    clang/test/Preprocessor/Inputs/print-header-json/system/system2.h
    clang/test/Preprocessor/Inputs/print-header-json/system/system3.h
    clang/test/Preprocessor/print-header-json.c

Modified: 
    clang/include/clang/Basic/DiagnosticDriverKinds.td
    clang/include/clang/Driver/Driver.h
    clang/include/clang/Driver/Options.td
    clang/include/clang/Frontend/DependencyOutputOptions.h
    clang/lib/Driver/Driver.cpp
    clang/lib/Driver/ToolChains/Clang.cpp
    clang/lib/Frontend/CompilerInvocation.cpp
    clang/lib/Frontend/HeaderIncludeGen.cpp
    clang/tools/driver/driver.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index 6479518454d5d..e8927161dcbe1 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -342,6 +342,13 @@ def err_drv_small_columns : Error<
 def err_drv_invalid_malign_branch_EQ : Error<
   "invalid argument '%0' to -malign-branch=; each element must be one of: %1">;
 
+def err_drv_print_header_env_var : Error<
+  "environment variable CC_PRINT_HEADERS_%select{FORMAT|FILTERING}0 has invalid value %1">;
+def err_drv_print_header_env_var_combination : Error<
+  "unsupported combination: CC_PRINT_HEADERS_FORMAT=%0 and CC_PRINT_HEADERS_FILTERING=%1">;
+def err_drv_print_header_env_var_combination_cc1 : Error<
+  "unsupported combination: -header-include-format=%0 and -header-include-filtering=%1">;
+
 def warn_O4_is_O3 : Warning<"-O4 is equivalent to -O3">, InGroup<Deprecated>;
 def warn_drv_optimization_value : Warning<"optimization level '%0' is not supported; using '%1%2' instead">,
   InGroup<InvalidCommandLineArgument>;

diff  --git a/clang/include/clang/Basic/HeaderInclude.h b/clang/include/clang/Basic/HeaderInclude.h
new file mode 100644
index 0000000000000..7a1a08ba617af
--- /dev/null
+++ b/clang/include/clang/Basic/HeaderInclude.h
@@ -0,0 +1,71 @@
+//===--- HeaderInclude.h - Header Include -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Defines enums used when emitting included header information.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_BASIC_HEADERINCLUDEFORMATKIND_H
+#define LLVM_CLANG_BASIC_HEADERINCLUDEFORMATKIND_H
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <utility>
+
+namespace clang {
+/// The format in which header information is emitted.
+enum HeaderIncludeFormatKind { HIFMT_None, HIFMT_Textual, HIFMT_JSON };
+
+/// Whether header information is filtered or not. If HIFIL_Only_Direct_System
+/// is used, only information on system headers directly included from
+/// non-system headers is emitted.
+enum HeaderIncludeFilteringKind { HIFIL_None, HIFIL_Only_Direct_System };
+
+inline HeaderIncludeFormatKind
+stringToHeaderIncludeFormatKind(const char *Str) {
+  return llvm::StringSwitch<HeaderIncludeFormatKind>(Str)
+      .Case("textual", HIFMT_Textual)
+      .Case("json", HIFMT_JSON)
+      .Default(HIFMT_None);
+}
+
+inline bool stringToHeaderIncludeFiltering(const char *Str,
+                                           HeaderIncludeFilteringKind &Kind) {
+  std::pair<bool, HeaderIncludeFilteringKind> P =
+      llvm::StringSwitch<std::pair<bool, HeaderIncludeFilteringKind>>(Str)
+          .Case("none", {true, HIFIL_None})
+          .Case("only-direct-system", {true, HIFIL_Only_Direct_System})
+          .Default({false, HIFIL_None});
+  Kind = P.second;
+  return P.first;
+}
+
+inline const char *headerIncludeFormatKindToString(HeaderIncludeFormatKind K) {
+  switch (K) {
+  case HIFMT_None:
+    llvm_unreachable("unexpected format kind");
+  case HIFMT_Textual:
+    return "textual";
+  case HIFMT_JSON:
+    return "json";
+  }
+}
+
+inline const char *
+headerIncludeFilteringKindToString(HeaderIncludeFilteringKind K) {
+  switch (K) {
+  case HIFIL_None:
+    return "none";
+  case HIFIL_Only_Direct_System:
+    return "only-direct-system";
+  }
+}
+
+} // end namespace clang
+
+#endif // LLVM_CLANG_BASIC_HEADERINCLUDEFORMATKIND_H

diff  --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h
index 6eb168e56510d..73cd0362c3b84 100644
--- a/clang/include/clang/Driver/Driver.h
+++ b/clang/include/clang/Driver/Driver.h
@@ -10,6 +10,7 @@
 #define LLVM_CLANG_DRIVER_DRIVER_H
 
 #include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/HeaderInclude.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Driver/Action.h"
 #include "clang/Driver/DriverDiagnostic.h"
@@ -233,9 +234,16 @@ class Driver {
   /// 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.
+  HeaderIncludeFormatKind CCPrintHeadersFormat = HIFMT_None;
+
+  /// This flag determines whether clang should filter the header information
+  /// that is emitted. If enviroment variable CC_PRINT_HEADERS_FILTERING is set
+  /// to "only-direct-system", only system headers that are directly included
+  /// from non-system headers are emitted.
+  HeaderIncludeFilteringKind CCPrintHeadersFiltering = HIFIL_None;
 
   /// 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
index c888d4f6194ab..fd0ca5f94a515 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -5675,6 +5675,14 @@ def module_file_deps : Flag<["-"], "module-file-deps">,
 def header_include_file : Separate<["-"], "header-include-file">,
   HelpText<"Filename (or -) to write header include output to">,
   MarshallingInfoString<DependencyOutputOpts<"HeaderIncludeOutputFile">>;
+def header_include_format_EQ : Joined<["-"], "header-include-format=">,
+  HelpText<"set format in which header info is emitted">,
+  Values<"textual,json">, NormalizedValues<["HIFMT_Textual", "HIFMT_JSON"]>,
+  MarshallingInfoEnum<DependencyOutputOpts<"HeaderIncludeFormat">, "HIFMT_Textual">;
+def header_include_filtering_EQ : Joined<["-"], "header-include-filtering=">,
+  HelpText<"set the flag that enables filtering header information">,
+  Values<"none,only-direct-system">, NormalizedValues<["HIFIL_None", "HIFIL_Only_Direct_System"]>,
+  MarshallingInfoEnum<DependencyOutputOpts<"HeaderIncludeFiltering">, "HIFIL_None">;
 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
index 78a2841d1e104..e0f445bb5970c 100644
--- a/clang/include/clang/Frontend/DependencyOutputOptions.h
+++ b/clang/include/clang/Frontend/DependencyOutputOptions.h
@@ -9,6 +9,7 @@
 #ifndef LLVM_CLANG_FRONTEND_DEPENDENCYOUTPUTOPTIONS_H
 #define LLVM_CLANG_FRONTEND_DEPENDENCYOUTPUTOPTIONS_H
 
+#include "clang/Basic/HeaderInclude.h"
 #include <string>
 #include <vector>
 
@@ -44,6 +45,12 @@ class DependencyOutputOptions {
                                           /// due to the "include guard
                                           /// optimization" or #pragma once.
 
+  /// The format of header information.
+  HeaderIncludeFormatKind HeaderIncludeFormat = HIFMT_Textual;
+
+  /// Determine whether header information should be filtered.
+  HeaderIncludeFilteringKind HeaderIncludeFiltering = HIFIL_None;
+
   /// Destination of cl.exe style /showIncludes info.
   ShowIncludesDestination ShowIncludesDest = ShowIncludesDestination::None;
 
@@ -80,7 +87,8 @@ class DependencyOutputOptions {
   DependencyOutputOptions()
       : IncludeSystemHeaders(0), ShowHeaderIncludes(0), UsePhonyTargets(0),
         AddMissingHeaderDeps(0), IncludeModuleFiles(0),
-        ShowSkippedHeaderIncludes(0) {}
+        ShowSkippedHeaderIncludes(0), HeaderIncludeFormat(HIFMT_Textual),
+        HeaderIncludeFiltering(HIFIL_None) {}
 };
 
 }  // end namespace clang

diff  --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index cf0a59688cec8..969d38560018b 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -198,7 +198,7 @@ Driver::Driver(StringRef ClangExecutable, StringRef TargetTriple,
       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
index 867445eec0eb8..b4d3c1e9a53a0 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -27,6 +27,7 @@
 #include "clang/Basic/CLWarnings.h"
 #include "clang/Basic/CharInfo.h"
 #include "clang/Basic/CodeGenOptions.h"
+#include "clang/Basic/HeaderInclude.h"
 #include "clang/Basic/LangOptions.h"
 #include "clang/Basic/MakeSupport.h"
 #include "clang/Basic/ObjCRuntime.h"
@@ -5556,12 +5557,19 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
   }
   Args.AddAllArgs(CmdArgs, options::OPT_fshow_skipped_includes);
 
-  if (D.CCPrintHeaders && !D.CCGenDiagnostics) {
+  if (D.CCPrintHeadersFormat && !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=" +
+        std::string(headerIncludeFormatKindToString(D.CCPrintHeadersFormat))));
+    CmdArgs.push_back(
+        Args.MakeArgString("-header-include-filtering=" +
+                           std::string(headerIncludeFilteringKindToString(
+                               D.CCPrintHeadersFiltering))));
   }
   Args.AddLastArg(CmdArgs, options::OPT_P);
   Args.AddLastArg(CmdArgs, options::OPT_print_ivar_layout);

diff  --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 5a8e92139854d..06a8815dd959b 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -2151,6 +2151,16 @@ static bool ParseDependencyOutputArgs(DependencyOutputOptions &Opts,
       Opts.ExtraDeps.emplace_back(std::string(Val), EDK_ModuleFile);
   }
 
+  // Check for invalid combinations of header-include-format
+  // and header-include-filtering.
+  if ((Opts.HeaderIncludeFormat == HIFMT_Textual &&
+       Opts.HeaderIncludeFiltering != HIFIL_None) ||
+      (Opts.HeaderIncludeFormat == HIFMT_JSON &&
+       Opts.HeaderIncludeFiltering != HIFIL_Only_Direct_System))
+    Diags.Report(diag::err_drv_print_header_env_var_combination_cc1)
+        << Args.getLastArg(OPT_header_include_format_EQ)->getValue()
+        << Args.getLastArg(OPT_header_include_filtering_EQ)->getValue();
+
   return Diags.getNumErrors() == NumErrorsBefore;
 }
 

diff  --git a/clang/lib/Frontend/HeaderIncludeGen.cpp b/clang/lib/Frontend/HeaderIncludeGen.cpp
index 5db8792bf4207..30fe5c75b9ac3 100644
--- 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 @@ class HeaderIncludesCallback : public PPCallbacks {
   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<std::string, 16> 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,33 @@ void clang::AttachHeaderIncludeGen(Preprocessor &PP,
     }
   }
 
-  // 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<HeaderIncludesCallback>(
-      &PP, ShowAllHeaders, OutputFile, DepOpts, OwnsOutputFile, ShowDepth,
-      MSStyle));
+  switch (DepOpts.HeaderIncludeFormat) {
+  case HIFMT_None:
+    llvm_unreachable("unexpected header format kind");
+  case HIFMT_Textual: {
+    assert(DepOpts.HeaderIncludeFiltering == HIFIL_None &&
+           "header filtering is currently always disabled when output format is"
+           "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<HeaderIncludesCallback>(
+        &PP, ShowAllHeaders, OutputFile, DepOpts, OwnsOutputFile, ShowDepth,
+        MSStyle));
+    break;
+  }
+  case HIFMT_JSON: {
+    assert(DepOpts.HeaderIncludeFiltering == HIFIL_Only_Direct_System &&
+           "only-direct-system is the only option for filtering");
+    PP.addPPCallbacks(std::make_unique<HeaderIncludesJSONCallback>(
+        &PP, OutputFile, OwnsOutputFile));
+    break;
+  }
+  }
 }
 
 void HeaderIncludesCallback::FileChanged(SourceLocation Loc,
@@ -197,3 +252,64 @@ void HeaderIncludesCallback::FileSkipped(const FileEntryRef &SkippedFile, const
   PrintHeaderInfo(OutputFile, SkippedFile.getName(), ShowDepth,
                   CurrentIncludeDepth + 1, MSStyle);
 }
+
+void HeaderIncludesJSONCallback::EndOfMainFile() {
+  const FileEntry *FE = SM.getFileEntryForID(SM.getMainFileID());
+  SmallString<256> MainFile(FE->getName());
+  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<llvm::raw_fd_ostream *>(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("<command line>"))
+    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
index 0000000000000..935ba1c0aa1f5
--- /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
index 0000000000000..e69de29bb2d1d

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
index 0000000000000..e69de29bb2d1d

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
index 0000000000000..0a892928669ce
--- /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
index 0000000000000..e69de29bb2d1d

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
index 0000000000000..e69de29bb2d1d

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
index 0000000000000..e69de29bb2d1d

diff  --git a/clang/test/Preprocessor/print-header-json.c b/clang/test/Preprocessor/print-header-json.c
new file mode 100644
index 0000000000000..6c5005ed5a541
--- /dev/null
+++ b/clang/test/Preprocessor/print-header-json.c
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -E -header-include-format=json -header-include-filtering=only-direct-system -header-include-file %t.txt -I %S/Inputs/print-header-json -isystem %S/Inputs/print-header-json/system %s
+// RUN: cat %t.txt | FileCheck %s --check-prefix=SUPPORTED
+// RUN: not %clang_cc1 -E -header-include-format=textual -header-include-filtering=only-direct-system -header-include-file %t.txt -I %S/Inputs/print-header-json -isystem %S/Inputs/print-header-json/system %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=UNSUPPORTED0
+// RUN: not %clang_cc1 -E -header-include-format=json -header-include-filtering=none -header-include-file %t.txt -I %S/Inputs/print-header-json -isystem %S/Inputs/print-header-json/system %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=UNSUPPORTED1
+// RUN: rm %t.txt
+// RUN: env CC_PRINT_HEADERS_FORMAT=json CC_PRINT_HEADERS_FILTERING=only-direct-system 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: env CC_PRINT_HEADERS_FORMAT=textual CC_PRINT_HEADERS_FILTERING=only-direct-system CC_PRINT_HEADERS_FILE=%t.txt not %clang -fsyntax-only -I %S/Inputs/print-header-json -isystem %S/Inputs/print-header-json/system %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=UNSUPPORTED2
+// RUN: env CC_PRINT_HEADERS_FORMAT=json CC_PRINT_HEADERS_FILTERING=none CC_PRINT_HEADERS_FILE=%t.txt not %clang -fsyntax-only -I %S/Inputs/print-header-json -isystem %S/Inputs/print-header-json/system %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=UNSUPPORTED3
+// RUN: cat %t.txt | FileCheck %s --check-prefix=SUPPORTED
+
+#include "system0.h"
+#include "header0.h"
+#include "system2.h"
+
+// SUPPORTED: {"source":"{{[^,]*}}/print-header-json.c","includes":["{{[^,]*}}system0.h","{{[^,]*}}system3.h","{{[^,]*}}system2.h"]}
+
+// UNSUPPORTED0: error: unsupported combination: -header-include-format=textual and -header-include-filtering=only-direct-system
+// UNSUPPORTED1: error: unsupported combination: -header-include-format=json and -header-include-filtering=none
+// UNSUPPORTED2: error: unsupported combination: CC_PRINT_HEADERS_FORMAT=textual and CC_PRINT_HEADERS_FILTERING=only-direct-system
+// UNSUPPORTED3: error: unsupported combination: CC_PRINT_HEADERS_FORMAT=json and CC_PRINT_HEADERS_FILTERING=none

diff  --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp
index 6529c45b79135..859bba52529a0 100644
--- a/clang/tools/driver/driver.cpp
+++ b/clang/tools/driver/driver.cpp
@@ -13,6 +13,7 @@
 
 #include "clang/Driver/Driver.h"
 #include "clang/Basic/DiagnosticOptions.h"
+#include "clang/Basic/HeaderInclude.h"
 #include "clang/Basic/Stack.h"
 #include "clang/Config/config.h"
 #include "clang/Driver/Compilation.h"
@@ -244,29 +245,61 @@ static void getCLEnvVarOptions(std::string &EnvValue, llvm::StringSaver &Saver,
       *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 <class T>
+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 bool 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<bool>("CC_PRINT_OPTIONS", "CC_PRINT_OPTIONS_FILE",
+                        TheDriver.CCPrintOptionsFilename);
+  if (checkEnvVar<bool>("CC_PRINT_HEADERS", "CC_PRINT_HEADERS_FILE",
+                        TheDriver.CCPrintHeadersFilename)) {
+    TheDriver.CCPrintHeadersFormat = HIFMT_Textual;
+    TheDriver.CCPrintHeadersFiltering = HIFIL_None;
+  } else if (const char *EnvVar = checkEnvVar<const char *>(
+                 "CC_PRINT_HEADERS_FORMAT", "CC_PRINT_HEADERS_FILE",
+                 TheDriver.CCPrintHeadersFilename)) {
+    TheDriver.CCPrintHeadersFormat = stringToHeaderIncludeFormatKind(EnvVar);
+    if (!TheDriver.CCPrintHeadersFormat) {
+      TheDriver.Diag(clang::diag::err_drv_print_header_env_var) << 0 << EnvVar;
+      return false;
+    }
+
+    const char *FilteringStr = ::getenv("CC_PRINT_HEADERS_FILTERING");
+    HeaderIncludeFilteringKind Filtering;
+    if (!stringToHeaderIncludeFiltering(FilteringStr, Filtering)) {
+      TheDriver.Diag(clang::diag::err_drv_print_header_env_var)
+          << 1 << FilteringStr;
+      return false;
+    }
+
+    if ((TheDriver.CCPrintHeadersFormat == HIFMT_Textual &&
+         Filtering != HIFIL_None) ||
+        (TheDriver.CCPrintHeadersFormat == HIFMT_JSON &&
+         Filtering != HIFIL_Only_Direct_System)) {
+      TheDriver.Diag(clang::diag::err_drv_print_header_env_var_combination)
+          << EnvVar << FilteringStr;
+      return false;
+    }
+    TheDriver.CCPrintHeadersFiltering = Filtering;
+  }
+
   TheDriver.CCLogDiagnostics =
-      CheckEnvVar("CC_LOG_DIAGNOSTICS", "CC_LOG_DIAGNOSTICS_FILE",
-                  TheDriver.CCLogDiagnosticsFilename);
+      checkEnvVar<bool>("CC_LOG_DIAGNOSTICS", "CC_LOG_DIAGNOSTICS_FILE",
+                        TheDriver.CCLogDiagnosticsFilename);
   TheDriver.CCPrintProcessStats =
-      CheckEnvVar("CC_PRINT_PROC_STAT", "CC_PRINT_PROC_STAT_FILE",
-                  TheDriver.CCPrintStatReportFilename);
+      checkEnvVar<bool>("CC_PRINT_PROC_STAT", "CC_PRINT_PROC_STAT_FILE",
+                        TheDriver.CCPrintStatReportFilename);
+
+  return true;
 }
 
 static void FixupDiagPrefixExeName(TextDiagnosticPrinter *DiagClient,
@@ -481,7 +514,8 @@ int clang_main(int Argc, char **Argv) {
 
   insertTargetAndModeArgs(TargetAndMode, Args, SavedStrings);
 
-  SetBackdoorDriverOutputsFromEnvVars(TheDriver);
+  if (!SetBackdoorDriverOutputsFromEnvVars(TheDriver))
+    return 1;
 
   if (!UseNewCC1Process) {
     TheDriver.CC1Main = &ExecuteCC1Tool;


        


More information about the cfe-commits mailing list