307 lines · cpp
1//=== unittests/Sema/ExternalSemaSourceTest.cpp - ExternalSemaSource tests ===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "clang/AST/ASTConsumer.h"10#include "clang/AST/ASTContext.h"11#include "clang/Frontend/CompilerInstance.h"12#include "clang/Lex/Preprocessor.h"13#include "clang/Parse/ParseAST.h"14#include "clang/Sema/ExternalSemaSource.h"15#include "clang/Sema/Sema.h"16#include "clang/Sema/SemaDiagnostic.h"17#include "clang/Sema/TypoCorrection.h"18#include "clang/Tooling/Tooling.h"19#include "gtest/gtest.h"20 21using namespace clang;22using namespace clang::tooling;23 24namespace {25 26// \brief Counts the number of times MaybeDiagnoseMissingCompleteType27// is called. Returns the result it was provided on creation.28class CompleteTypeDiagnoser : public clang::ExternalSemaSource {29public:30 CompleteTypeDiagnoser(bool MockResult) : CallCount(0), Result(MockResult) {}31 32 bool MaybeDiagnoseMissingCompleteType(SourceLocation L, QualType T) override {33 ++CallCount;34 return Result;35 }36 37 int CallCount;38 bool Result;39};40 41/// Counts the number of typo-correcting diagnostics correcting from one name to42/// another while still passing all diagnostics along a chain of consumers.43class DiagnosticWatcher : public clang::DiagnosticConsumer {44 DiagnosticConsumer *Chained;45 std::string FromName;46 std::string ToName;47 48public:49 DiagnosticWatcher(StringRef From, StringRef To)50 : Chained(nullptr), FromName(From), ToName("'"), SeenCount(0) {51 ToName.append(std::string(To));52 ToName.append("'");53 }54 55 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,56 const Diagnostic &Info) override {57 if (Chained)58 Chained->HandleDiagnostic(DiagLevel, Info);59 if (Info.getID() - 1 == diag::err_using_directive_member_suggest) {60 const IdentifierInfo *Ident = Info.getArgIdentifier(0);61 const std::string &CorrectedQuotedStr = Info.getArgStdStr(1);62 if (Ident->getName() == FromName && CorrectedQuotedStr == ToName)63 ++SeenCount;64 } else if (Info.getID() == diag::err_no_member_suggest) {65 auto Ident = DeclarationName::getFromOpaqueInteger(Info.getRawArg(0));66 const std::string &CorrectedQuotedStr = Info.getArgStdStr(3);67 if (Ident.getAsString() == FromName && CorrectedQuotedStr == ToName)68 ++SeenCount;69 }70 }71 72 void clear() override {73 DiagnosticConsumer::clear();74 if (Chained)75 Chained->clear();76 }77 78 bool IncludeInDiagnosticCounts() const override {79 if (Chained)80 return Chained->IncludeInDiagnosticCounts();81 return false;82 }83 84 DiagnosticWatcher *Chain(DiagnosticConsumer *ToChain) {85 Chained = ToChain;86 return this;87 }88 89 int SeenCount;90};91 92// \brief Always corrects a typo matching CorrectFrom with a new namespace93// with the name CorrectTo.94class NamespaceTypoProvider : public clang::ExternalSemaSource {95 std::string CorrectFrom;96 std::string CorrectTo;97 Sema *CurrentSema;98 99public:100 NamespaceTypoProvider(StringRef From, StringRef To)101 : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}102 103 void InitializeSema(Sema &S) override { CurrentSema = &S; }104 105 void ForgetSema() override { CurrentSema = nullptr; }106 107 TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,108 Scope *S, CXXScopeSpec *SS,109 CorrectionCandidateCallback &CCC,110 DeclContext *MemberContext, bool EnteringContext,111 const ObjCObjectPointerType *OPT) override {112 ++CallCount;113 if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {114 DeclContext *DestContext = nullptr;115 ASTContext &Context = CurrentSema->getASTContext();116 if (SS)117 DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);118 if (!DestContext)119 DestContext = Context.getTranslationUnitDecl();120 IdentifierInfo *ToIdent =121 CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);122 NamespaceDecl *NewNamespace =123 NamespaceDecl::Create(Context, DestContext, false, Typo.getBeginLoc(),124 Typo.getLoc(), ToIdent, nullptr, false);125 DestContext->addDecl(NewNamespace);126 TypoCorrection Correction(ToIdent);127 Correction.addCorrectionDecl(NewNamespace);128 return Correction;129 }130 return TypoCorrection();131 }132 133 int CallCount;134};135 136class FunctionTypoProvider : public clang::ExternalSemaSource {137 std::string CorrectFrom;138 std::string CorrectTo;139 Sema *CurrentSema;140 141public:142 FunctionTypoProvider(StringRef From, StringRef To)143 : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}144 145 void InitializeSema(Sema &S) override { CurrentSema = &S; }146 147 void ForgetSema() override { CurrentSema = nullptr; }148 149 TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,150 Scope *S, CXXScopeSpec *SS,151 CorrectionCandidateCallback &CCC,152 DeclContext *MemberContext, bool EnteringContext,153 const ObjCObjectPointerType *OPT) override {154 ++CallCount;155 if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {156 DeclContext *DestContext = nullptr;157 ASTContext &Context = CurrentSema->getASTContext();158 if (SS)159 DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);160 if (!DestContext)161 DestContext = Context.getTranslationUnitDecl();162 IdentifierInfo *ToIdent =163 CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);164 auto *NewFunction = FunctionDecl::Create(165 Context, DestContext, SourceLocation(), SourceLocation(), ToIdent,166 Context.getFunctionType(Context.VoidTy, {}, {}), nullptr, SC_Static,167 /*UsesFPIntrin*/ false);168 DestContext->addDecl(NewFunction);169 TypoCorrection Correction(ToIdent);170 Correction.addCorrectionDecl(NewFunction);171 return Correction;172 }173 return TypoCorrection();174 }175 176 int CallCount;177};178 179// \brief Chains together a vector of DiagnosticWatchers and180// adds a vector of ExternalSemaSources to the CompilerInstance before181// performing semantic analysis.182class ExternalSemaSourceInstaller : public clang::ASTFrontendAction {183 std::vector<DiagnosticWatcher *> Watchers;184 std::vector<llvm::IntrusiveRefCntPtr<clang::ExternalSemaSource>> Sources;185 std::unique_ptr<DiagnosticConsumer> OwnedClient;186 187protected:188 std::unique_ptr<clang::ASTConsumer>189 CreateASTConsumer(clang::CompilerInstance &Compiler,190 llvm::StringRef /* dummy */) override {191 return std::make_unique<clang::ASTConsumer>();192 }193 194 void ExecuteAction() override {195 CompilerInstance &CI = getCompilerInstance();196 ASSERT_FALSE(CI.hasSema());197 CI.createSema(getTranslationUnitKind(), nullptr);198 ASSERT_TRUE(CI.hasDiagnostics());199 DiagnosticsEngine &Diagnostics = CI.getDiagnostics();200 DiagnosticConsumer *Client = Diagnostics.getClient();201 if (Diagnostics.ownsClient())202 OwnedClient = Diagnostics.takeClient();203 for (size_t I = 0, E = Watchers.size(); I < E; ++I)204 Client = Watchers[I]->Chain(Client);205 Diagnostics.setClient(Client, false);206 for (size_t I = 0, E = Sources.size(); I < E; ++I) {207 Sources[I]->InitializeSema(CI.getSema());208 CI.getSema().addExternalSource(Sources[I]);209 }210 ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats,211 CI.getFrontendOpts().SkipFunctionBodies);212 }213 214public:215 void PushSource(llvm::IntrusiveRefCntPtr<clang::ExternalSemaSource> Source) {216 Sources.push_back(std::move(Source));217 }218 219 void PushWatcher(DiagnosticWatcher *Watcher) { Watchers.push_back(Watcher); }220};221 222using llvm::makeIntrusiveRefCnt;223 224// Make sure that the DiagnosticWatcher is not miscounting.225TEST(ExternalSemaSource, DiagCheck) {226 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();227 DiagnosticWatcher Watcher("AAB", "BBB");228 Installer->PushWatcher(&Watcher);229 std::vector<std::string> Args(1, "-std=c++11");230 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(231 std::move(Installer), "namespace AAA { } using namespace AAB;", Args));232 ASSERT_EQ(0, Watcher.SeenCount);233}234 235// Check that when we add a NamespaceTypeProvider, we use that suggestion236// instead of the usual suggestion we would use above.237TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) {238 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();239 auto Provider = makeIntrusiveRefCnt<NamespaceTypoProvider>("AAB", "BBB");240 DiagnosticWatcher Watcher("AAB", "BBB");241 Installer->PushSource(Provider);242 Installer->PushWatcher(&Watcher);243 std::vector<std::string> Args(1, "-std=c++11");244 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(245 std::move(Installer), "namespace AAA { } using namespace AAB;", Args));246 ASSERT_LE(0, Provider->CallCount);247 ASSERT_EQ(1, Watcher.SeenCount);248}249 250// Check that we use the first successful TypoCorrection returned from an251// ExternalSemaSource.252TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) {253 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();254 auto First = makeIntrusiveRefCnt<NamespaceTypoProvider>("XXX", "BBB");255 auto Second = makeIntrusiveRefCnt<NamespaceTypoProvider>("AAB", "CCC");256 auto Third = makeIntrusiveRefCnt<NamespaceTypoProvider>("AAB", "DDD");257 DiagnosticWatcher Watcher("AAB", "CCC");258 Installer->PushSource(First);259 Installer->PushSource(Second);260 Installer->PushSource(Third);261 Installer->PushWatcher(&Watcher);262 std::vector<std::string> Args(1, "-std=c++11");263 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(264 std::move(Installer), "namespace AAA { } using namespace AAB;", Args));265 ASSERT_LE(1, First->CallCount);266 ASSERT_LE(1, Second->CallCount);267 ASSERT_EQ(0, Third->CallCount);268 ASSERT_EQ(1, Watcher.SeenCount);269}270 271// We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise272// solve the problem.273TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {274 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();275 auto Diagnoser = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(false);276 Installer->PushSource(Diagnoser);277 std::vector<std::string> Args(1, "-std=c++11");278 // This code hits the class template specialization/class member of a class279 // template specialization checks in Sema::RequireCompleteTypeImpl.280 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(281 std::move(Installer),282 "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",283 Args));284 ASSERT_EQ(0, Diagnoser->CallCount);285}286 287// The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns288// true should be the last one called.289TEST(ExternalSemaSource, FirstDiagnoserTaken) {290 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();291 auto First = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(false);292 auto Second = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(true);293 auto Third = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(true);294 Installer->PushSource(First);295 Installer->PushSource(Second);296 Installer->PushSource(Third);297 std::vector<std::string> Args(1, "-std=c++11");298 ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(299 std::move(Installer), "class Incomplete; Incomplete IncompleteInstance;",300 Args));301 ASSERT_EQ(1, First->CallCount);302 ASSERT_EQ(1, Second->CallCount);303 ASSERT_EQ(0, Third->CallCount);304}305 306} // anonymous namespace307