This revision was automatically updated to reflect the committed changes.
Closed by commit rG419001724542: [clangd] Add tests covering existing 
header-guard behavior. NFC (authored by sammccall).

Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D106201/new/

https://reviews.llvm.org/D106201

Files:
  clang-tools-extra/clangd/unittests/ParsedASTTests.cpp

Index: clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
+++ clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
@@ -46,6 +46,7 @@
 using ::testing::AllOf;
 using ::testing::ElementsAre;
 using ::testing::ElementsAreArray;
+using ::testing::IsEmpty;
 
 MATCHER_P(DeclNamed, Name, "") {
   if (NamedDecl *ND = dyn_cast<NamedDecl>(arg))
@@ -633,6 +634,260 @@
                 testPath("foo.cpp"))));
 }
 
+// Returns Code guarded by #ifndef guards
+std::string guard(llvm::StringRef Code) {
+  static int GuardID = 0;
+  std::string GuardName = ("GUARD_" + llvm::Twine(++GuardID)).str();
+  return llvm::formatv("#ifndef {0}\n#define {0}\n{1}\n#endif\n", GuardName,
+                       Code);
+}
+
+std::string once(llvm::StringRef Code) {
+  return llvm::formatv("#pragma once\n{0}\n", Code);
+}
+
+bool mainIsGuarded(const ParsedAST &AST) {
+  const auto &SM = AST.getSourceManager();
+  const FileEntry *MainFE = SM.getFileEntryForID(SM.getMainFileID());
+  return AST.getPreprocessor()
+      .getHeaderSearchInfo()
+      .isFileMultipleIncludeGuarded(MainFE);
+}
+
+MATCHER_P(Diag, Desc, "") {
+  return llvm::StringRef(arg.Message).contains(Desc);
+}
+
+// Check our understanding of whether the main file is header guarded or not.
+TEST(ParsedASTTest, HeaderGuards) {
+  TestTU TU;
+  TU.ImplicitHeaderGuard = false;
+
+  TU.Code = ";";
+  EXPECT_FALSE(mainIsGuarded(TU.build()));
+
+  TU.Code = guard(";");
+  EXPECT_FALSE(mainIsGuarded(TU.build())); // FIXME: true
+
+  TU.Code = once(";");
+  EXPECT_FALSE(mainIsGuarded(TU.build())); // FIXME: true
+
+  TU.Code = R"cpp(
+    ;
+    #pragma once
+  )cpp";
+  EXPECT_FALSE(mainIsGuarded(TU.build())); // FIXME: true
+
+  TU.Code = R"cpp(
+    ;
+    #ifndef GUARD
+    #define GUARD
+    ;
+    #endif
+  )cpp";
+  EXPECT_FALSE(mainIsGuarded(TU.build()));
+}
+
+// Check our handling of files that include themselves.
+// Ideally we allow this if the file has header guards.
+//
+// Note: the semicolons (empty statements) are significant!
+// - they force the preamble to end and the body to begin. Directives can have
+//   different effects in the preamble vs main file (which we try to hide).
+// - if the preamble would otherwise cover the whole file, a trailing semicolon
+//   forces their sizes to be different. This is significant because the file
+//   size is part of the lookup key for HeaderFileInfo, and we don't want to
+//   rely on the preamble's HFI being looked up when parsing the main file.
+TEST(ParsedASTTest, HeaderGuardsSelfInclude) {
+  TestTU TU;
+  TU.ImplicitHeaderGuard = false;
+  TU.Filename = "self.h";
+
+  TU.Code = R"cpp(
+    #include "self.h" // error-ok
+    ;
+  )cpp";
+  auto AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(),
+              ElementsAre(Diag("recursively when building a preamble")));
+  EXPECT_FALSE(mainIsGuarded(AST));
+
+  TU.Code = R"cpp(
+    ;
+    #include "self.h" // error-ok
+  )cpp";
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(), ElementsAre(Diag("nested too deeply")));
+  EXPECT_FALSE(mainIsGuarded(AST));
+
+  TU.Code = R"cpp(
+    #pragma once
+    #include "self.h"
+    ;
+  )cpp";
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(), IsEmpty());
+  EXPECT_FALSE(mainIsGuarded(AST)); // FIXME: true
+
+  TU.Code = R"cpp(
+    #pragma once
+    ;
+    #include "self.h"
+  )cpp";
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(), IsEmpty());
+  EXPECT_TRUE(mainIsGuarded(AST));
+
+  TU.Code = R"cpp(
+    ;
+    #pragma once
+    #include "self.h"
+  )cpp";
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(), IsEmpty());
+  EXPECT_TRUE(mainIsGuarded(AST));
+
+  TU.Code = R"cpp(
+    #ifndef GUARD
+    #define GUARD
+    #include "self.h" // error-ok: FIXME, this would be nice to support
+    #endif
+    ;
+  )cpp";
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(),
+              ElementsAre(Diag("recursively when building a preamble")));
+  EXPECT_FALSE(mainIsGuarded(AST)); // FIXME: true
+
+  TU.Code = R"cpp(
+    #ifndef GUARD
+    #define GUARD
+    ;
+    #include "self.h"
+    #endif
+  )cpp";
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(), IsEmpty());
+  EXPECT_FALSE(mainIsGuarded(AST)); // FIXME: true
+
+  // Guarded too late...
+  TU.Code = R"cpp(
+    #include "self.h" // error-ok
+    #ifndef GUARD
+    #define GUARD
+    ;
+    #endif
+  )cpp";
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(),
+              ElementsAre(Diag("recursively when building a preamble")));
+  EXPECT_FALSE(mainIsGuarded(AST));
+
+  TU.Code = R"cpp(
+    #include "self.h" // error-ok
+    ;
+    #ifndef GUARD
+    #define GUARD
+    #endif
+  )cpp";
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(),
+              ElementsAre(Diag("recursively when building a preamble")));
+  EXPECT_FALSE(mainIsGuarded(AST));
+
+  TU.Code = R"cpp(
+    ;
+    #ifndef GUARD
+    #define GUARD
+    #include "self.h"
+    #endif
+  )cpp";
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(), IsEmpty());
+  EXPECT_FALSE(mainIsGuarded(AST));
+
+  TU.Code = R"cpp(
+    #include "self.h" // error-ok
+    #pragma once
+    ;
+  )cpp";
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(),
+              ElementsAre(Diag("recursively when building a preamble")));
+  EXPECT_FALSE(mainIsGuarded(AST)); // FIXME: true
+
+  TU.Code = R"cpp(
+    #include "self.h" // error-ok
+    ;
+    #pragma once
+  )cpp";
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(),
+              ElementsAre(Diag("recursively when building a preamble")));
+  EXPECT_TRUE(mainIsGuarded(AST));
+}
+
+// Tests how we handle common idioms for splitting a header-only library
+// into interface and implementation files (e.g. *.h vs *.inl).
+// These files mutually include each other, and need careful handling of include
+// guards (which interact with preambles).
+TEST(ParsedASTTest, HeaderGuardsImplIface) {
+  std::string Interface = R"cpp(
+    // error-ok: we assert on diagnostics explicitly
+    template <class T> struct Traits {
+      unsigned size();
+    };
+    #include "impl.h"
+  )cpp";
+  std::string Implementation = R"cpp(
+    // error-ok: we assert on diagnostics explicitly
+    #include "iface.h"
+    template <class T> unsigned Traits<T>::size() {
+      return sizeof(T);
+    }
+  )cpp";
+
+  TestTU TU;
+  TU.ImplicitHeaderGuard = false; // We're testing include guard handling!
+  TU.ExtraArgs.push_back("-xc++-header");
+
+  // Editing the interface file, which is include guarded (easy case).
+  // We mostly get this right via PP if we don't recognize the include guard.
+  TU.Filename = "iface.h";
+  TU.Code = guard(Interface);
+  TU.AdditionalFiles = {{"impl.h", Implementation}};
+  auto AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(), IsEmpty());
+  EXPECT_FALSE(mainIsGuarded(AST)); // FIXME: true
+  // Slightly harder: the `#pragma once` is part of the preamble, and we
+  // need to transfer it to the main file's HeaderFileInfo.
+  TU.Code = once(Interface);
+  AST = TU.build();
+  // FIXME: empty
+  EXPECT_THAT(*AST.getDiagnostics(),
+              ElementsAre(Diag("in included file: redefinition of 'Traits'")));
+  EXPECT_TRUE(mainIsGuarded(AST));
+
+  // Editing the implementation file, which is not include guarded.
+  TU.Filename = "impl.h";
+  TU.Code = Implementation;
+  TU.AdditionalFiles = {{"iface.h", guard(Interface)}};
+  AST = TU.build();
+  // The diagnostic is unfortunate in this case, but correct per our model.
+  // Ultimately the include is skipped and the code is parsed correctly though.
+  EXPECT_THAT(*AST.getDiagnostics(),
+              ElementsAre(Diag("in included file: main file cannot be included "
+                               "recursively when building a preamble")));
+  EXPECT_FALSE(mainIsGuarded(AST));
+  // Interface is pragma once guarded, same thing.
+  TU.AdditionalFiles = {{"iface.h", once(Interface)}};
+  AST = TU.build();
+  EXPECT_THAT(*AST.getDiagnostics(),
+              ElementsAre(Diag("in included file: main file cannot be included "
+                               "recursively when building a preamble")));
+  EXPECT_FALSE(mainIsGuarded(AST));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to