1 //=== unittests/Sema/ExternalSemaSourceTest.cpp - ExternalSemaSource tests ===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "clang/AST/ASTConsumer.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/Frontend/CompilerInstance.h"
13 #include "clang/Lex/Preprocessor.h"
14 #include "clang/Parse/ParseAST.h"
15 #include "clang/Sema/ExternalSemaSource.h"
16 #include "clang/Sema/Sema.h"
17 #include "clang/Sema/SemaDiagnostic.h"
18 #include "clang/Sema/TypoCorrection.h"
19 #include "clang/Tooling/Tooling.h"
20 #include "gtest/gtest.h"
21 
22 using namespace clang;
23 using namespace clang::tooling;
24 
25 namespace {
26 
27 // \brief Counts the number of times MaybeDiagnoseMissingCompleteType
28 // is called. Returns the result it was provided on creation.
29 class CompleteTypeDiagnoser : public clang::ExternalSemaSource {
30 public:
CompleteTypeDiagnoser(bool MockResult)31   CompleteTypeDiagnoser(bool MockResult) : CallCount(0), Result(MockResult) {}
32 
MaybeDiagnoseMissingCompleteType(SourceLocation L,QualType T)33   bool MaybeDiagnoseMissingCompleteType(SourceLocation L, QualType T) override {
34     ++CallCount;
35     return Result;
36   }
37 
38   int CallCount;
39   bool Result;
40 };
41 
42 /// Counts the number of typo-correcting diagnostics correcting from one name to
43 /// another while still passing all diagnostics along a chain of consumers.
44 class DiagnosticWatcher : public clang::DiagnosticConsumer {
45   DiagnosticConsumer *Chained;
46   std::string FromName;
47   std::string ToName;
48 
49 public:
DiagnosticWatcher(StringRef From,StringRef To)50   DiagnosticWatcher(StringRef From, StringRef To)
51       : Chained(nullptr), FromName(From), ToName("'"), SeenCount(0) {
52     ToName.append(To);
53     ToName.append("'");
54   }
55 
HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,const Diagnostic & Info)56   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
57                         const Diagnostic &Info) override {
58     if (Chained)
59       Chained->HandleDiagnostic(DiagLevel, Info);
60     if (Info.getID() - 1 == diag::err_using_directive_member_suggest) {
61       const IdentifierInfo *Ident = Info.getArgIdentifier(0);
62       const std::string &CorrectedQuotedStr = Info.getArgStdStr(1);
63       if (Ident->getName() == FromName && CorrectedQuotedStr == ToName)
64         ++SeenCount;
65     } else if (Info.getID() == diag::err_no_member_suggest) {
66       auto Ident = DeclarationName::getFromOpaqueInteger(Info.getRawArg(0));
67       const std::string &CorrectedQuotedStr = Info.getArgStdStr(3);
68       if (Ident.getAsString() == FromName && CorrectedQuotedStr == ToName)
69         ++SeenCount;
70     }
71   }
72 
clear()73   void clear() override {
74     DiagnosticConsumer::clear();
75     if (Chained)
76       Chained->clear();
77   }
78 
IncludeInDiagnosticCounts() const79   bool IncludeInDiagnosticCounts() const override {
80     if (Chained)
81       return Chained->IncludeInDiagnosticCounts();
82     return false;
83   }
84 
Chain(DiagnosticConsumer * ToChain)85   DiagnosticWatcher *Chain(DiagnosticConsumer *ToChain) {
86     Chained = ToChain;
87     return this;
88   }
89 
90   int SeenCount;
91 };
92 
93 // \brief Always corrects a typo matching CorrectFrom with a new namespace
94 // with the name CorrectTo.
95 class NamespaceTypoProvider : public clang::ExternalSemaSource {
96   std::string CorrectFrom;
97   std::string CorrectTo;
98   Sema *CurrentSema;
99 
100 public:
NamespaceTypoProvider(StringRef From,StringRef To)101   NamespaceTypoProvider(StringRef From, StringRef To)
102       : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
103 
InitializeSema(Sema & S)104   void InitializeSema(Sema &S) override { CurrentSema = &S; }
105 
ForgetSema()106   void ForgetSema() override { CurrentSema = nullptr; }
107 
CorrectTypo(const DeclarationNameInfo & Typo,int LookupKind,Scope * S,CXXScopeSpec * SS,CorrectionCandidateCallback & CCC,DeclContext * MemberContext,bool EnteringContext,const ObjCObjectPointerType * OPT)108   TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
109                              Scope *S, CXXScopeSpec *SS,
110                              CorrectionCandidateCallback &CCC,
111                              DeclContext *MemberContext, bool EnteringContext,
112                              const ObjCObjectPointerType *OPT) override {
113     ++CallCount;
114     if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {
115       DeclContext *DestContext = nullptr;
116       ASTContext &Context = CurrentSema->getASTContext();
117       if (SS)
118         DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);
119       if (!DestContext)
120         DestContext = Context.getTranslationUnitDecl();
121       IdentifierInfo *ToIdent =
122           CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
123       NamespaceDecl *NewNamespace =
124           NamespaceDecl::Create(Context, DestContext, false, Typo.getBeginLoc(),
125                                 Typo.getLoc(), ToIdent, nullptr);
126       DestContext->addDecl(NewNamespace);
127       TypoCorrection Correction(ToIdent);
128       Correction.addCorrectionDecl(NewNamespace);
129       return Correction;
130     }
131     return TypoCorrection();
132   }
133 
134   int CallCount;
135 };
136 
137 class FunctionTypoProvider : public clang::ExternalSemaSource {
138   std::string CorrectFrom;
139   std::string CorrectTo;
140   Sema *CurrentSema;
141 
142 public:
FunctionTypoProvider(StringRef From,StringRef To)143   FunctionTypoProvider(StringRef From, StringRef To)
144       : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
145 
InitializeSema(Sema & S)146   void InitializeSema(Sema &S) override { CurrentSema = &S; }
147 
ForgetSema()148   void ForgetSema() override { CurrentSema = nullptr; }
149 
CorrectTypo(const DeclarationNameInfo & Typo,int LookupKind,Scope * S,CXXScopeSpec * SS,CorrectionCandidateCallback & CCC,DeclContext * MemberContext,bool EnteringContext,const ObjCObjectPointerType * OPT)150   TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
151                              Scope *S, CXXScopeSpec *SS,
152                              CorrectionCandidateCallback &CCC,
153                              DeclContext *MemberContext, bool EnteringContext,
154                              const ObjCObjectPointerType *OPT) override {
155     ++CallCount;
156     if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {
157       DeclContext *DestContext = nullptr;
158       ASTContext &Context = CurrentSema->getASTContext();
159       if (SS)
160         DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);
161       if (!DestContext)
162         DestContext = Context.getTranslationUnitDecl();
163       IdentifierInfo *ToIdent =
164           CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
165       auto *NewFunction = FunctionDecl::Create(
166           Context, DestContext, SourceLocation(), SourceLocation(), ToIdent,
167           Context.getFunctionType(Context.VoidTy, {}, {}), nullptr, SC_Static);
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 llvm::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 // Make sure that the DiagnosticWatcher is not miscounting.
TEST(ExternalSemaSource,SanityCheck)223 TEST(ExternalSemaSource, SanityCheck) {
224   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
225       new ExternalSemaSourceInstaller);
226   DiagnosticWatcher Watcher("AAB", "BBB");
227   Installer->PushWatcher(&Watcher);
228   std::vector<std::string> Args(1, "-std=c++11");
229   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
230       Installer.release(), "namespace AAA { } using namespace AAB;", Args));
231   ASSERT_EQ(0, Watcher.SeenCount);
232 }
233 
234 // Check that when we add a NamespaceTypeProvider, we use that suggestion
235 // instead of the usual suggestion we would use above.
TEST(ExternalSemaSource,ExternalTypoCorrectionPrioritized)236 TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) {
237   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
238       new ExternalSemaSourceInstaller);
239   NamespaceTypoProvider Provider("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       Installer.release(), "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   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
254       new ExternalSemaSourceInstaller);
255   NamespaceTypoProvider First("XXX", "BBB");
256   NamespaceTypoProvider Second("AAB", "CCC");
257   NamespaceTypoProvider Third("AAB", "DDD");
258   DiagnosticWatcher Watcher("AAB", "CCC");
259   Installer->PushSource(&First);
260   Installer->PushSource(&Second);
261   Installer->PushSource(&Third);
262   Installer->PushWatcher(&Watcher);
263   std::vector<std::string> Args(1, "-std=c++11");
264   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
265       Installer.release(), "namespace AAA { } using namespace AAB;", Args));
266   ASSERT_LE(1, First.CallCount);
267   ASSERT_LE(1, Second.CallCount);
268   ASSERT_EQ(0, Third.CallCount);
269   ASSERT_EQ(1, Watcher.SeenCount);
270 }
271 
TEST(ExternalSemaSource,ExternalDelayedTypoCorrection)272 TEST(ExternalSemaSource, ExternalDelayedTypoCorrection) {
273   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
274       new ExternalSemaSourceInstaller);
275   FunctionTypoProvider Provider("aaa", "bbb");
276   DiagnosticWatcher Watcher("aaa", "bbb");
277   Installer->PushSource(&Provider);
278   Installer->PushWatcher(&Watcher);
279   std::vector<std::string> Args(1, "-std=c++11");
280   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
281       Installer.release(), "namespace AAA { } void foo() { AAA::aaa(); }",
282       Args));
283   ASSERT_LE(0, Provider.CallCount);
284   ASSERT_EQ(1, Watcher.SeenCount);
285 }
286 
287 // We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
288 // solve the problem.
TEST(ExternalSemaSource,TryOtherTacticsBeforeDiagnosing)289 TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {
290   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
291       new ExternalSemaSourceInstaller);
292   CompleteTypeDiagnoser Diagnoser(false);
293   Installer->PushSource(&Diagnoser);
294   std::vector<std::string> Args(1, "-std=c++11");
295   // This code hits the class template specialization/class member of a class
296   // template specialization checks in Sema::RequireCompleteTypeImpl.
297   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
298       Installer.release(),
299       "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
300       Args));
301   ASSERT_EQ(0, Diagnoser.CallCount);
302 }
303 
304 // The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
305 // true should be the last one called.
TEST(ExternalSemaSource,FirstDiagnoserTaken)306 TEST(ExternalSemaSource, FirstDiagnoserTaken) {
307   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
308       new ExternalSemaSourceInstaller);
309   CompleteTypeDiagnoser First(false);
310   CompleteTypeDiagnoser Second(true);
311   CompleteTypeDiagnoser Third(true);
312   Installer->PushSource(&First);
313   Installer->PushSource(&Second);
314   Installer->PushSource(&Third);
315   std::vector<std::string> Args(1, "-std=c++11");
316   ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(
317       Installer.release(), "class Incomplete; Incomplete IncompleteInstance;",
318       Args));
319   ASSERT_EQ(1, First.CallCount);
320   ASSERT_EQ(1, Second.CallCount);
321   ASSERT_EQ(0, Third.CallCount);
322 }
323 
324 } // anonymous namespace
325