sammccall created this revision.
sammccall added a reviewer: kadircet.
Herald added subscribers: usaxena95, arphaman.
Herald added a project: All.
sammccall requested review of this revision.
Herald added subscribers: cfe-commits, MaskRay, ilya-biryukov.
Herald added projects: clang, clang-tools-extra.

If clang is passed "-include foo.h", it will rewrite to "-include-pch foo.h.pch"
before passing it to cc1, if foo.h.pch exists.

Existence is checked, but validity is not. This is probably a reasonable
assumption for the compiler itself, but not for clang-based tools where the
actual compiler may be a different version of clang, or even GCC.
In the end, we lose our -include, we gain a -include-pch that can't be used,
and the file often fails to parse.

I would like to turn this off for all non-clang invocations (i.e.
createInvocationFromCommandLine), but we have explicit tests of this behavior
for libclang and I can't work out the implications of changing it.

Instead this patch:

- makes it optional in the driver, default on (no change)
- makes it optional in createInvocationFromCommandLine, default on (no change)
- changes driver to do IO through the VFS so it can be tested
- tests the option
- turns the option off in clangd where the problem was reported

Subsequent patches should make libclang opt in explicitly and flip the default
for all other tools. It's probably also time to extract an options struct
for createInvocationFromCommandLine.

Fixes https://github.com/clangd/clangd/issues/856
Fixes https://github.com/clangd/vscode-clangd/issues/324


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D124970

Files:
  clang-tools-extra/clangd/Compiler.cpp
  clang/include/clang/Driver/Driver.h
  clang/include/clang/Frontend/Utils.h
  clang/lib/Driver/Driver.cpp
  clang/lib/Driver/ToolChains/Clang.cpp
  clang/lib/Frontend/CreateInvocationFromCommandLine.cpp
  clang/unittests/Frontend/UtilsTest.cpp

Index: clang/unittests/Frontend/UtilsTest.cpp
===================================================================
--- clang/unittests/Frontend/UtilsTest.cpp
+++ clang/unittests/Frontend/UtilsTest.cpp
@@ -11,12 +11,15 @@
 #include "clang/Basic/TargetOptions.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/CompilerInvocation.h"
+#include "clang/Lex/PreprocessorOptions.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/Support/VirtualFileSystem.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 namespace clang {
 namespace {
+using testing::ElementsAre;
 
 TEST(BuildCompilerInvocationTest, RecoverMultipleJobs) {
   // This generates multiple jobs and we recover by using the first.
@@ -33,5 +36,31 @@
   EXPECT_THAT(CI->TargetOpts->Triple, testing::StartsWith("i386-"));
 }
 
+// buildInvocationFromCommandLine should not translate -include to -include-pch,
+// even if the PCH file exists.
+TEST(BuildCompilerInvocationTest, ProbePrecompiled) {
+  std::vector<const char *> Args = {"clang", "-include", "foo.h", "foo.cpp"};
+  auto FS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
+  FS->addFile("foo.h", 0, llvm::MemoryBuffer::getMemBuffer(""));
+  FS->addFile("foo.h.pch", 0, llvm::MemoryBuffer::getMemBuffer(""));
+
+  clang::IgnoringDiagConsumer D;
+  llvm::IntrusiveRefCntPtr<DiagnosticsEngine> CommandLineDiagsEngine =
+      clang::CompilerInstance::createDiagnostics(new DiagnosticOptions, &D,
+                                                 false);
+  // Default: ProbePrecompiled is true.
+  std::unique_ptr<CompilerInvocation> CI = createInvocationFromCommandLine(
+      Args, CommandLineDiagsEngine, FS, false, nullptr);
+  ASSERT_TRUE(CI);
+  EXPECT_THAT(CI->getPreprocessorOpts().Includes, ElementsAre());
+  EXPECT_EQ(CI->getPreprocessorOpts().ImplicitPCHInclude, "foo.h.pch");
+
+  CI = createInvocationFromCommandLine(Args, CommandLineDiagsEngine, FS, false,
+                                       nullptr, /*ProbePrecompiled=*/false);
+  ASSERT_TRUE(CI);
+  EXPECT_THAT(CI->getPreprocessorOpts().Includes, ElementsAre("foo.h"));
+  EXPECT_EQ(CI->getPreprocessorOpts().ImplicitPCHInclude, "");
+}
+
 } // namespace
 } // namespace clang
Index: clang/lib/Frontend/CreateInvocationFromCommandLine.cpp
===================================================================
--- clang/lib/Frontend/CreateInvocationFromCommandLine.cpp
+++ clang/lib/Frontend/CreateInvocationFromCommandLine.cpp
@@ -29,7 +29,7 @@
 std::unique_ptr<CompilerInvocation> clang::createInvocationFromCommandLine(
     ArrayRef<const char *> ArgList, IntrusiveRefCntPtr<DiagnosticsEngine> Diags,
     IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS, bool ShouldRecoverOnErorrs,
-    std::vector<std::string> *CC1Args) {
+    std::vector<std::string> *CC1Args, bool ProbePrecompiled) {
   assert(!ArgList.empty());
   if (!Diags.get()) {
     // No diagnostics engine was provided, so create our own diagnostics object
@@ -51,6 +51,9 @@
 
   // Don't check that inputs exist, they may have been remapped.
   TheDriver.setCheckInputsExist(false);
+  // Don't probe on the filesystem for PCHes of -include'd headers - we may have
+  // a VFS, they may be misversioned, etc.
+  TheDriver.setProbePrecompiled(ProbePrecompiled);
 
   std::unique_ptr<driver::Compilation> C(TheDriver.BuildCompilation(Args));
   if (!C)
Index: clang/lib/Driver/ToolChains/Clang.cpp
===================================================================
--- clang/lib/Driver/ToolChains/Clang.cpp
+++ clang/lib/Driver/ToolChains/Clang.cpp
@@ -1364,7 +1364,8 @@
 
   bool RenderedImplicitInclude = false;
   for (const Arg *A : Args.filtered(options::OPT_clang_i_Group)) {
-    if (A->getOption().matches(options::OPT_include)) {
+    if (A->getOption().matches(options::OPT_include) &&
+        D.getProbePrecompiled()) {
       // Handling of gcc-style gch precompiled headers.
       bool IsFirstImplicitInclude = !RenderedImplicitInclude;
       RenderedImplicitInclude = true;
@@ -1375,12 +1376,12 @@
       // so that replace_extension does the right thing.
       P += ".dummy";
       llvm::sys::path::replace_extension(P, "pch");
-      if (llvm::sys::fs::exists(P))
+      if (D.getVFS().exists(P))
         FoundPCH = true;
 
       if (!FoundPCH) {
         llvm::sys::path::replace_extension(P, "gch");
-        if (llvm::sys::fs::exists(P)) {
+        if (D.getVFS().exists(P)) {
           FoundPCH = true;
         }
       }
Index: clang/lib/Driver/Driver.cpp
===================================================================
--- clang/lib/Driver/Driver.cpp
+++ clang/lib/Driver/Driver.cpp
@@ -198,7 +198,8 @@
       CCPrintOptions(false), CCPrintHeaders(false), CCLogDiagnostics(false),
       CCGenDiagnostics(false), CCPrintProcessStats(false),
       TargetTriple(TargetTriple), Saver(Alloc), CheckInputsExist(true),
-      GenReproducer(false), SuppressMissingInputWarning(false) {
+      ProbePrecompiled(true), GenReproducer(false),
+      SuppressMissingInputWarning(false) {
   // Provide a sane fallback if no VFS is specified.
   if (!this->VFS)
     this->VFS = llvm::vfs::getRealFileSystem();
Index: clang/include/clang/Frontend/Utils.h
===================================================================
--- clang/include/clang/Frontend/Utils.h
+++ clang/include/clang/Frontend/Utils.h
@@ -200,6 +200,10 @@
 /// \param CC1Args - if non-null, will be populated with the args to cc1
 /// expanded from \p Args. May be set even if nullptr is returned.
 ///
+/// \param ProbePrecompiled - allow the driver to probe the filesystem for
+/// PCH files to replace -include with.
+/// FIXME: ProbePrecompiled=true is a poor, historical default.
+///
 /// \return A CompilerInvocation, or nullptr if none was built for the given
 /// argument vector.
 std::unique_ptr<CompilerInvocation> createInvocationFromCommandLine(
@@ -208,7 +212,7 @@
         IntrusiveRefCntPtr<DiagnosticsEngine>(),
     IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS = nullptr,
     bool ShouldRecoverOnErrors = false,
-    std::vector<std::string> *CC1Args = nullptr);
+    std::vector<std::string> *CC1Args = nullptr, bool ProbePrecompiled = true);
 
 // Frontend timing utils
 
Index: clang/include/clang/Driver/Driver.h
===================================================================
--- clang/include/clang/Driver/Driver.h
+++ clang/include/clang/Driver/Driver.h
@@ -271,6 +271,9 @@
   /// Whether to check that input files exist when constructing compilation
   /// jobs.
   unsigned CheckInputsExist : 1;
+  /// Whether to probe for PCH files on disk,
+  /// in order to upgrade -include foo.h to -include-pch foo.h.pch.
+  unsigned ProbePrecompiled : 1;
 
 public:
   /// Force clang to emit reproducer for driver invocation. This is enabled
@@ -357,6 +360,9 @@
 
   void setCheckInputsExist(bool Value) { CheckInputsExist = Value; }
 
+  bool getProbePrecompiled() const { return ProbePrecompiled; }
+  void setProbePrecompiled(bool Value) { ProbePrecompiled = Value; }
+
   void setTargetAndMode(const ParsedClangName &TM) { ClangNameParts = TM; }
 
   const std::string &getTitle() { return DriverTitle; }
Index: clang-tools-extra/clangd/Compiler.cpp
===================================================================
--- clang-tools-extra/clangd/Compiler.cpp
+++ clang-tools-extra/clangd/Compiler.cpp
@@ -96,7 +96,7 @@
       CompilerInstance::createDiagnostics(new DiagnosticOptions, &D, false);
   std::unique_ptr<CompilerInvocation> CI = createInvocationFromCommandLine(
       ArgStrs, CommandLineDiagsEngine, std::move(VFS),
-      /*ShouldRecoverOnErrors=*/true, CC1Args);
+      /*ShouldRecoverOnErrors=*/true, CC1Args, /*ProbePrecompiled=*/false);
   if (!CI)
     return nullptr;
   // createInvocationFromCommandLine sets DisableFree.
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to