forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			318 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			318 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
| //=== unittests/Sema/ExternalSemaSourceTest.cpp - ExternalSemaSource tests ===//
 | |
| //
 | |
| // 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
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| #include "clang/AST/ASTConsumer.h"
 | |
| #include "clang/AST/ASTContext.h"
 | |
| #include "clang/Frontend/CompilerInstance.h"
 | |
| #include "clang/Lex/Preprocessor.h"
 | |
| #include "clang/Parse/ParseAST.h"
 | |
| #include "clang/Sema/ExternalSemaSource.h"
 | |
| #include "clang/Sema/Sema.h"
 | |
| #include "clang/Sema/SemaDiagnostic.h"
 | |
| #include "clang/Sema/TypoCorrection.h"
 | |
| #include "clang/Tooling/Tooling.h"
 | |
| #include "gtest/gtest.h"
 | |
| 
 | |
| using namespace clang;
 | |
| using namespace clang::tooling;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| // \brief Counts the number of times MaybeDiagnoseMissingCompleteType
 | |
| // is called. Returns the result it was provided on creation.
 | |
| class CompleteTypeDiagnoser : public clang::ExternalSemaSource {
 | |
| public:
 | |
|   CompleteTypeDiagnoser(bool MockResult) : CallCount(0), Result(MockResult) {}
 | |
| 
 | |
|   bool MaybeDiagnoseMissingCompleteType(SourceLocation L, QualType T) override {
 | |
|     ++CallCount;
 | |
|     return Result;
 | |
|   }
 | |
| 
 | |
|   int CallCount;
 | |
|   bool Result;
 | |
| };
 | |
| 
 | |
| /// Counts the number of typo-correcting diagnostics correcting from one name to
 | |
| /// another while still passing all diagnostics along a chain of consumers.
 | |
| class DiagnosticWatcher : public clang::DiagnosticConsumer {
 | |
|   DiagnosticConsumer *Chained;
 | |
|   std::string FromName;
 | |
|   std::string ToName;
 | |
| 
 | |
| public:
 | |
|   DiagnosticWatcher(StringRef From, StringRef To)
 | |
|       : Chained(nullptr), FromName(From), ToName("'"), SeenCount(0) {
 | |
|     ToName.append(To);
 | |
|     ToName.append("'");
 | |
|   }
 | |
| 
 | |
|   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
 | |
|                         const Diagnostic &Info) override {
 | |
|     if (Chained)
 | |
|       Chained->HandleDiagnostic(DiagLevel, Info);
 | |
|     if (Info.getID() - 1 == diag::err_using_directive_member_suggest) {
 | |
|       const IdentifierInfo *Ident = Info.getArgIdentifier(0);
 | |
|       const std::string &CorrectedQuotedStr = Info.getArgStdStr(1);
 | |
|       if (Ident->getName() == FromName && CorrectedQuotedStr == ToName)
 | |
|         ++SeenCount;
 | |
|     } else if (Info.getID() == diag::err_no_member_suggest) {
 | |
|       auto Ident = DeclarationName::getFromOpaqueInteger(Info.getRawArg(0));
 | |
|       const std::string &CorrectedQuotedStr = Info.getArgStdStr(3);
 | |
|       if (Ident.getAsString() == FromName && CorrectedQuotedStr == ToName)
 | |
|         ++SeenCount;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void clear() override {
 | |
|     DiagnosticConsumer::clear();
 | |
|     if (Chained)
 | |
|       Chained->clear();
 | |
|   }
 | |
| 
 | |
|   bool IncludeInDiagnosticCounts() const override {
 | |
|     if (Chained)
 | |
|       return Chained->IncludeInDiagnosticCounts();
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   DiagnosticWatcher *Chain(DiagnosticConsumer *ToChain) {
 | |
|     Chained = ToChain;
 | |
|     return this;
 | |
|   }
 | |
| 
 | |
|   int SeenCount;
 | |
| };
 | |
| 
 | |
| // \brief Always corrects a typo matching CorrectFrom with a new namespace
 | |
| // with the name CorrectTo.
 | |
| class NamespaceTypoProvider : public clang::ExternalSemaSource {
 | |
|   std::string CorrectFrom;
 | |
|   std::string CorrectTo;
 | |
|   Sema *CurrentSema;
 | |
| 
 | |
| public:
 | |
|   NamespaceTypoProvider(StringRef From, StringRef To)
 | |
|       : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
 | |
| 
 | |
|   void InitializeSema(Sema &S) override { CurrentSema = &S; }
 | |
| 
 | |
|   void ForgetSema() override { CurrentSema = nullptr; }
 | |
| 
 | |
|   TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
 | |
|                              Scope *S, CXXScopeSpec *SS,
 | |
|                              CorrectionCandidateCallback &CCC,
 | |
|                              DeclContext *MemberContext, bool EnteringContext,
 | |
|                              const ObjCObjectPointerType *OPT) override {
 | |
|     ++CallCount;
 | |
|     if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {
 | |
|       DeclContext *DestContext = nullptr;
 | |
|       ASTContext &Context = CurrentSema->getASTContext();
 | |
|       if (SS)
 | |
|         DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);
 | |
|       if (!DestContext)
 | |
|         DestContext = Context.getTranslationUnitDecl();
 | |
|       IdentifierInfo *ToIdent =
 | |
|           CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
 | |
|       NamespaceDecl *NewNamespace =
 | |
|           NamespaceDecl::Create(Context, DestContext, false, Typo.getBeginLoc(),
 | |
|                                 Typo.getLoc(), ToIdent, nullptr);
 | |
|       DestContext->addDecl(NewNamespace);
 | |
|       TypoCorrection Correction(ToIdent);
 | |
|       Correction.addCorrectionDecl(NewNamespace);
 | |
|       return Correction;
 | |
|     }
 | |
|     return TypoCorrection();
 | |
|   }
 | |
| 
 | |
|   int CallCount;
 | |
| };
 | |
| 
 | |
| class FunctionTypoProvider : public clang::ExternalSemaSource {
 | |
|   std::string CorrectFrom;
 | |
|   std::string CorrectTo;
 | |
|   Sema *CurrentSema;
 | |
| 
 | |
| public:
 | |
|   FunctionTypoProvider(StringRef From, StringRef To)
 | |
|       : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
 | |
| 
 | |
|   void InitializeSema(Sema &S) override { CurrentSema = &S; }
 | |
| 
 | |
|   void ForgetSema() override { CurrentSema = nullptr; }
 | |
| 
 | |
|   TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
 | |
|                              Scope *S, CXXScopeSpec *SS,
 | |
|                              CorrectionCandidateCallback &CCC,
 | |
|                              DeclContext *MemberContext, bool EnteringContext,
 | |
|                              const ObjCObjectPointerType *OPT) override {
 | |
|     ++CallCount;
 | |
|     if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {
 | |
|       DeclContext *DestContext = nullptr;
 | |
|       ASTContext &Context = CurrentSema->getASTContext();
 | |
|       if (SS)
 | |
|         DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);
 | |
|       if (!DestContext)
 | |
|         DestContext = Context.getTranslationUnitDecl();
 | |
|       IdentifierInfo *ToIdent =
 | |
|           CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
 | |
|       auto *NewFunction = FunctionDecl::Create(
 | |
|           Context, DestContext, SourceLocation(), SourceLocation(), ToIdent,
 | |
|           Context.getFunctionType(Context.VoidTy, {}, {}), nullptr, SC_Static);
 | |
|       DestContext->addDecl(NewFunction);
 | |
|       TypoCorrection Correction(ToIdent);
 | |
|       Correction.addCorrectionDecl(NewFunction);
 | |
|       return Correction;
 | |
|     }
 | |
|     return TypoCorrection();
 | |
|   }
 | |
| 
 | |
|   int CallCount;
 | |
| };
 | |
| 
 | |
| // \brief Chains together a vector of DiagnosticWatchers and
 | |
| // adds a vector of ExternalSemaSources to the CompilerInstance before
 | |
| // performing semantic analysis.
 | |
| class ExternalSemaSourceInstaller : public clang::ASTFrontendAction {
 | |
|   std::vector<DiagnosticWatcher *> Watchers;
 | |
|   std::vector<clang::ExternalSemaSource *> Sources;
 | |
|   std::unique_ptr<DiagnosticConsumer> OwnedClient;
 | |
| 
 | |
| protected:
 | |
|   std::unique_ptr<clang::ASTConsumer>
 | |
|   CreateASTConsumer(clang::CompilerInstance &Compiler,
 | |
|                     llvm::StringRef /* dummy */) override {
 | |
|     return std::make_unique<clang::ASTConsumer>();
 | |
|   }
 | |
| 
 | |
|   void ExecuteAction() override {
 | |
|     CompilerInstance &CI = getCompilerInstance();
 | |
|     ASSERT_FALSE(CI.hasSema());
 | |
|     CI.createSema(getTranslationUnitKind(), nullptr);
 | |
|     ASSERT_TRUE(CI.hasDiagnostics());
 | |
|     DiagnosticsEngine &Diagnostics = CI.getDiagnostics();
 | |
|     DiagnosticConsumer *Client = Diagnostics.getClient();
 | |
|     if (Diagnostics.ownsClient())
 | |
|       OwnedClient = Diagnostics.takeClient();
 | |
|     for (size_t I = 0, E = Watchers.size(); I < E; ++I)
 | |
|       Client = Watchers[I]->Chain(Client);
 | |
|     Diagnostics.setClient(Client, false);
 | |
|     for (size_t I = 0, E = Sources.size(); I < E; ++I) {
 | |
|       Sources[I]->InitializeSema(CI.getSema());
 | |
|       CI.getSema().addExternalSource(Sources[I]);
 | |
|     }
 | |
|     ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats,
 | |
|              CI.getFrontendOpts().SkipFunctionBodies);
 | |
|   }
 | |
| 
 | |
| public:
 | |
|   void PushSource(clang::ExternalSemaSource *Source) {
 | |
|     Sources.push_back(Source);
 | |
|   }
 | |
| 
 | |
|   void PushWatcher(DiagnosticWatcher *Watcher) { Watchers.push_back(Watcher); }
 | |
| };
 | |
| 
 | |
| // Make sure that the DiagnosticWatcher is not miscounting.
 | |
| TEST(ExternalSemaSource, SanityCheck) {
 | |
|   auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
 | |
|   DiagnosticWatcher Watcher("AAB", "BBB");
 | |
|   Installer->PushWatcher(&Watcher);
 | |
|   std::vector<std::string> Args(1, "-std=c++11");
 | |
|   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
 | |
|       std::move(Installer), "namespace AAA { } using namespace AAB;", Args));
 | |
|   ASSERT_EQ(0, Watcher.SeenCount);
 | |
| }
 | |
| 
 | |
| // Check that when we add a NamespaceTypeProvider, we use that suggestion
 | |
| // instead of the usual suggestion we would use above.
 | |
| TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) {
 | |
|   auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
 | |
|   NamespaceTypoProvider Provider("AAB", "BBB");
 | |
|   DiagnosticWatcher Watcher("AAB", "BBB");
 | |
|   Installer->PushSource(&Provider);
 | |
|   Installer->PushWatcher(&Watcher);
 | |
|   std::vector<std::string> Args(1, "-std=c++11");
 | |
|   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
 | |
|       std::move(Installer), "namespace AAA { } using namespace AAB;", Args));
 | |
|   ASSERT_LE(0, Provider.CallCount);
 | |
|   ASSERT_EQ(1, Watcher.SeenCount);
 | |
| }
 | |
| 
 | |
| // Check that we use the first successful TypoCorrection returned from an
 | |
| // ExternalSemaSource.
 | |
| TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) {
 | |
|   auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
 | |
|   NamespaceTypoProvider First("XXX", "BBB");
 | |
|   NamespaceTypoProvider Second("AAB", "CCC");
 | |
|   NamespaceTypoProvider Third("AAB", "DDD");
 | |
|   DiagnosticWatcher Watcher("AAB", "CCC");
 | |
|   Installer->PushSource(&First);
 | |
|   Installer->PushSource(&Second);
 | |
|   Installer->PushSource(&Third);
 | |
|   Installer->PushWatcher(&Watcher);
 | |
|   std::vector<std::string> Args(1, "-std=c++11");
 | |
|   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
 | |
|       std::move(Installer), "namespace AAA { } using namespace AAB;", Args));
 | |
|   ASSERT_LE(1, First.CallCount);
 | |
|   ASSERT_LE(1, Second.CallCount);
 | |
|   ASSERT_EQ(0, Third.CallCount);
 | |
|   ASSERT_EQ(1, Watcher.SeenCount);
 | |
| }
 | |
| 
 | |
| TEST(ExternalSemaSource, ExternalDelayedTypoCorrection) {
 | |
|   auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
 | |
|   FunctionTypoProvider Provider("aaa", "bbb");
 | |
|   DiagnosticWatcher Watcher("aaa", "bbb");
 | |
|   Installer->PushSource(&Provider);
 | |
|   Installer->PushWatcher(&Watcher);
 | |
|   std::vector<std::string> Args(1, "-std=c++11");
 | |
|   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
 | |
|       std::move(Installer), "namespace AAA { } void foo() { AAA::aaa(); }",
 | |
|       Args));
 | |
|   ASSERT_LE(0, Provider.CallCount);
 | |
|   ASSERT_EQ(1, Watcher.SeenCount);
 | |
| }
 | |
| 
 | |
| // We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
 | |
| // solve the problem.
 | |
| TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {
 | |
|   auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
 | |
|   CompleteTypeDiagnoser Diagnoser(false);
 | |
|   Installer->PushSource(&Diagnoser);
 | |
|   std::vector<std::string> Args(1, "-std=c++11");
 | |
|   // This code hits the class template specialization/class member of a class
 | |
|   // template specialization checks in Sema::RequireCompleteTypeImpl.
 | |
|   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
 | |
|       std::move(Installer),
 | |
|       "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
 | |
|       Args));
 | |
|   ASSERT_EQ(0, Diagnoser.CallCount);
 | |
| }
 | |
| 
 | |
| // The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
 | |
| // true should be the last one called.
 | |
| TEST(ExternalSemaSource, FirstDiagnoserTaken) {
 | |
|   auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
 | |
|   CompleteTypeDiagnoser First(false);
 | |
|   CompleteTypeDiagnoser Second(true);
 | |
|   CompleteTypeDiagnoser Third(true);
 | |
|   Installer->PushSource(&First);
 | |
|   Installer->PushSource(&Second);
 | |
|   Installer->PushSource(&Third);
 | |
|   std::vector<std::string> Args(1, "-std=c++11");
 | |
|   ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(
 | |
|       std::move(Installer), "class Incomplete; Incomplete IncompleteInstance;",
 | |
|       Args));
 | |
|   ASSERT_EQ(1, First.CallCount);
 | |
|   ASSERT_EQ(1, Second.CallCount);
 | |
|   ASSERT_EQ(0, Third.CallCount);
 | |
| }
 | |
| 
 | |
| } // anonymous namespace
 |