xref: /llvm-project/clang/unittests/Sema/ExternalSemaSourceTest.cpp (revision 15e76eed0c7662f8a4bce849a58637070d3b0a75)
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-exception
6 //
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 
21 using namespace clang;
22 using namespace clang::tooling;
23 
24 namespace {
25 
26 // \brief Counts the number of times MaybeDiagnoseMissingCompleteType
27 // is called. Returns the result it was provided on creation.
28 class CompleteTypeDiagnoser : public clang::ExternalSemaSource {
29 public:
CompleteTypeDiagnoser(bool MockResult)30   CompleteTypeDiagnoser(bool MockResult) : CallCount(0), Result(MockResult) {}
31 
MaybeDiagnoseMissingCompleteType(SourceLocation L,QualType T)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 to
42 /// another while still passing all diagnostics along a chain of consumers.
43 class DiagnosticWatcher : public clang::DiagnosticConsumer {
44   DiagnosticConsumer *Chained;
45   std::string FromName;
46   std::string ToName;
47 
48 public:
DiagnosticWatcher(StringRef From,StringRef To)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 
HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,const Diagnostic & Info)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 
clear()72   void clear() override {
73     DiagnosticConsumer::clear();
74     if (Chained)
75       Chained->clear();
76   }
77 
IncludeInDiagnosticCounts() const78   bool IncludeInDiagnosticCounts() const override {
79     if (Chained)
80       return Chained->IncludeInDiagnosticCounts();
81     return false;
82   }
83 
Chain(DiagnosticConsumer * ToChain)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 namespace
93 // with the name CorrectTo.
94 class NamespaceTypoProvider : public clang::ExternalSemaSource {
95   std::string CorrectFrom;
96   std::string CorrectTo;
97   Sema *CurrentSema;
98 
99 public:
NamespaceTypoProvider(StringRef From,StringRef To)100   NamespaceTypoProvider(StringRef From, StringRef To)
101       : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
102 
InitializeSema(Sema & S)103   void InitializeSema(Sema &S) override { CurrentSema = &S; }
104 
ForgetSema()105   void ForgetSema() override { CurrentSema = nullptr; }
106 
CorrectTypo(const DeclarationNameInfo & Typo,int LookupKind,Scope * S,CXXScopeSpec * SS,CorrectionCandidateCallback & CCC,DeclContext * MemberContext,bool EnteringContext,const ObjCObjectPointerType * OPT)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 
136 class FunctionTypoProvider : public clang::ExternalSemaSource {
137   std::string CorrectFrom;
138   std::string CorrectTo;
139   Sema *CurrentSema;
140 
141 public:
FunctionTypoProvider(StringRef From,StringRef To)142   FunctionTypoProvider(StringRef From, StringRef To)
143       : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
144 
InitializeSema(Sema & S)145   void InitializeSema(Sema &S) override { CurrentSema = &S; }
146 
ForgetSema()147   void ForgetSema() override { CurrentSema = nullptr; }
148 
CorrectTypo(const DeclarationNameInfo & Typo,int LookupKind,Scope * S,CXXScopeSpec * SS,CorrectionCandidateCallback & CCC,DeclContext * MemberContext,bool EnteringContext,const ObjCObjectPointerType * OPT)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 and
180 // adds a vector of ExternalSemaSources to the CompilerInstance before
181 // performing semantic analysis.
182 class ExternalSemaSourceInstaller : public clang::ASTFrontendAction {
183   std::vector<DiagnosticWatcher *> Watchers;
184   std::vector<clang::ExternalSemaSource *> Sources;
185   std::unique_ptr<DiagnosticConsumer> OwnedClient;
186 
187 protected:
188   std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance & Compiler,llvm::StringRef)189   CreateASTConsumer(clang::CompilerInstance &Compiler,
190                     llvm::StringRef /* dummy */) override {
191     return std::make_unique<clang::ASTConsumer>();
192   }
193 
ExecuteAction()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 
214 public:
PushSource(clang::ExternalSemaSource * Source)215   void PushSource(clang::ExternalSemaSource *Source) {
216     Sources.push_back(Source);
217   }
218 
PushWatcher(DiagnosticWatcher * Watcher)219   void PushWatcher(DiagnosticWatcher *Watcher) { Watchers.push_back(Watcher); }
220 };
221 
222 using llvm::makeIntrusiveRefCnt;
223 
224 // Make sure that the DiagnosticWatcher is not miscounting.
TEST(ExternalSemaSource,DiagCheck)225 TEST(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 suggestion
236 // instead of the usual suggestion we would use above.
TEST(ExternalSemaSource,ExternalTypoCorrectionPrioritized)237 TEST(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.get());
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 an
251 // ExternalSemaSource.
TEST(ExternalSemaSource,ExternalTypoCorrectionOrdering)252 TEST(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.get());
259   Installer->PushSource(Second.get());
260   Installer->PushSource(Third.get());
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 
TEST(ExternalSemaSource,ExternalDelayedTypoCorrection)271 TEST(ExternalSemaSource, ExternalDelayedTypoCorrection) {
272   auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
273   auto Provider = makeIntrusiveRefCnt<FunctionTypoProvider>("aaa", "bbb");
274   DiagnosticWatcher Watcher("aaa", "bbb");
275   Installer->PushSource(Provider.get());
276   Installer->PushWatcher(&Watcher);
277   std::vector<std::string> Args(1, "-std=c++11");
278   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
279       std::move(Installer), "namespace AAA { } void foo() { AAA::aaa(); }",
280       Args));
281   ASSERT_LE(0, Provider->CallCount);
282   ASSERT_EQ(1, Watcher.SeenCount);
283 }
284 
285 // We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
286 // solve the problem.
TEST(ExternalSemaSource,TryOtherTacticsBeforeDiagnosing)287 TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {
288   auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
289   auto Diagnoser = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(false);
290   Installer->PushSource(Diagnoser.get());
291   std::vector<std::string> Args(1, "-std=c++11");
292   // This code hits the class template specialization/class member of a class
293   // template specialization checks in Sema::RequireCompleteTypeImpl.
294   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
295       std::move(Installer),
296       "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
297       Args));
298   ASSERT_EQ(0, Diagnoser->CallCount);
299 }
300 
301 // The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
302 // true should be the last one called.
TEST(ExternalSemaSource,FirstDiagnoserTaken)303 TEST(ExternalSemaSource, FirstDiagnoserTaken) {
304   auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
305   auto First = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(false);
306   auto Second = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(true);
307   auto Third = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(true);
308   Installer->PushSource(First.get());
309   Installer->PushSource(Second.get());
310   Installer->PushSource(Third.get());
311   std::vector<std::string> Args(1, "-std=c++11");
312   ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(
313       std::move(Installer), "class Incomplete; Incomplete IncompleteInstance;",
314       Args));
315   ASSERT_EQ(1, First->CallCount);
316   ASSERT_EQ(1, Second->CallCount);
317   ASSERT_EQ(0, Third->CallCount);
318 }
319 
320 } // anonymous namespace
321