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 // \brief Counts the number of err_using_directive_member_suggest diagnostics
43 // correcting from one namespace to another while still passing all diagnostics
44 // along a chain of consumers.
45 class NamespaceDiagnosticWatcher : public clang::DiagnosticConsumer {
46   DiagnosticConsumer *Chained;
47   std::string FromNS;
48   std::string ToNS;
49 
50 public:
NamespaceDiagnosticWatcher(StringRef From,StringRef To)51   NamespaceDiagnosticWatcher(StringRef From, StringRef To)
52       : Chained(nullptr), FromNS(From), ToNS("'"), SeenCount(0) {
53     ToNS.append(To);
54     ToNS.append("'");
55   }
56 
HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,const Diagnostic & Info)57   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
58                         const Diagnostic &Info) override {
59     if (Chained)
60       Chained->HandleDiagnostic(DiagLevel, Info);
61     if (Info.getID() - 1 == diag::err_using_directive_member_suggest) {
62       const IdentifierInfo *Ident = Info.getArgIdentifier(0);
63       const std::string &CorrectedQuotedStr = Info.getArgStdStr(1);
64       if (Ident->getName() == FromNS && CorrectedQuotedStr == ToNS)
65         ++SeenCount;
66     }
67   }
68 
clear()69   void clear() override {
70     DiagnosticConsumer::clear();
71     if (Chained)
72       Chained->clear();
73   }
74 
IncludeInDiagnosticCounts() const75   bool IncludeInDiagnosticCounts() const override {
76     if (Chained)
77       return Chained->IncludeInDiagnosticCounts();
78     return false;
79   }
80 
Chain(DiagnosticConsumer * ToChain)81   NamespaceDiagnosticWatcher *Chain(DiagnosticConsumer *ToChain) {
82     Chained = ToChain;
83     return this;
84   }
85 
86   int SeenCount;
87 };
88 
89 // \brief Always corrects a typo matching CorrectFrom with a new namespace
90 // with the name CorrectTo.
91 class NamespaceTypoProvider : public clang::ExternalSemaSource {
92   std::string CorrectFrom;
93   std::string CorrectTo;
94   Sema *CurrentSema;
95 
96 public:
NamespaceTypoProvider(StringRef From,StringRef To)97   NamespaceTypoProvider(StringRef From, StringRef To)
98       : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
99 
InitializeSema(Sema & S)100   void InitializeSema(Sema &S) override { CurrentSema = &S; }
101 
ForgetSema()102   void ForgetSema() override { CurrentSema = nullptr; }
103 
CorrectTypo(const DeclarationNameInfo & Typo,int LookupKind,Scope * S,CXXScopeSpec * SS,CorrectionCandidateCallback & CCC,DeclContext * MemberContext,bool EnteringContext,const ObjCObjectPointerType * OPT)104   TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
105                              Scope *S, CXXScopeSpec *SS,
106                              CorrectionCandidateCallback &CCC,
107                              DeclContext *MemberContext, bool EnteringContext,
108                              const ObjCObjectPointerType *OPT) override {
109     ++CallCount;
110     if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {
111       DeclContext *DestContext = nullptr;
112       ASTContext &Context = CurrentSema->getASTContext();
113       if (SS)
114         DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);
115       if (!DestContext)
116         DestContext = Context.getTranslationUnitDecl();
117       IdentifierInfo *ToIdent =
118           CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
119       NamespaceDecl *NewNamespace =
120           NamespaceDecl::Create(Context, DestContext, false, Typo.getBeginLoc(),
121                                 Typo.getLoc(), ToIdent, nullptr);
122       DestContext->addDecl(NewNamespace);
123       TypoCorrection Correction(ToIdent);
124       Correction.addCorrectionDecl(NewNamespace);
125       return Correction;
126     }
127     return TypoCorrection();
128   }
129 
130   int CallCount;
131 };
132 
133 // \brief Chains together a vector of NamespaceDiagnosticWatchers and
134 // adds a vector of ExternalSemaSources to the CompilerInstance before
135 // performing semantic analysis.
136 class ExternalSemaSourceInstaller : public clang::ASTFrontendAction {
137   std::vector<NamespaceDiagnosticWatcher *> Watchers;
138   std::vector<clang::ExternalSemaSource *> Sources;
139   std::unique_ptr<DiagnosticConsumer> OwnedClient;
140 
141 protected:
142   std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance & Compiler,llvm::StringRef)143   CreateASTConsumer(clang::CompilerInstance &Compiler,
144                     llvm::StringRef /* dummy */) override {
145     return llvm::make_unique<clang::ASTConsumer>();
146   }
147 
ExecuteAction()148   void ExecuteAction() override {
149     CompilerInstance &CI = getCompilerInstance();
150     ASSERT_FALSE(CI.hasSema());
151     CI.createSema(getTranslationUnitKind(), nullptr);
152     ASSERT_TRUE(CI.hasDiagnostics());
153     DiagnosticsEngine &Diagnostics = CI.getDiagnostics();
154     DiagnosticConsumer *Client = Diagnostics.getClient();
155     if (Diagnostics.ownsClient())
156       OwnedClient = Diagnostics.takeClient();
157     for (size_t I = 0, E = Watchers.size(); I < E; ++I)
158       Client = Watchers[I]->Chain(Client);
159     Diagnostics.setClient(Client, false);
160     for (size_t I = 0, E = Sources.size(); I < E; ++I) {
161       Sources[I]->InitializeSema(CI.getSema());
162       CI.getSema().addExternalSource(Sources[I]);
163     }
164     ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats,
165              CI.getFrontendOpts().SkipFunctionBodies);
166   }
167 
168 public:
PushSource(clang::ExternalSemaSource * Source)169   void PushSource(clang::ExternalSemaSource *Source) {
170     Sources.push_back(Source);
171   }
172 
PushWatcher(NamespaceDiagnosticWatcher * Watcher)173   void PushWatcher(NamespaceDiagnosticWatcher *Watcher) {
174     Watchers.push_back(Watcher);
175   }
176 };
177 
178 // Make sure that the NamespaceDiagnosticWatcher is not miscounting.
TEST(ExternalSemaSource,SanityCheck)179 TEST(ExternalSemaSource, SanityCheck) {
180   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
181       new ExternalSemaSourceInstaller);
182   NamespaceDiagnosticWatcher Watcher("AAB", "BBB");
183   Installer->PushWatcher(&Watcher);
184   std::vector<std::string> Args(1, "-std=c++11");
185   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
186       Installer.release(), "namespace AAA { } using namespace AAB;", Args));
187   ASSERT_EQ(0, Watcher.SeenCount);
188 }
189 
190 // Check that when we add a NamespaceTypeProvider, we use that suggestion
191 // instead of the usual suggestion we would use above.
TEST(ExternalSemaSource,ExternalTypoCorrectionPrioritized)192 TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) {
193   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
194       new ExternalSemaSourceInstaller);
195   NamespaceTypoProvider Provider("AAB", "BBB");
196   NamespaceDiagnosticWatcher Watcher("AAB", "BBB");
197   Installer->PushSource(&Provider);
198   Installer->PushWatcher(&Watcher);
199   std::vector<std::string> Args(1, "-std=c++11");
200   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
201       Installer.release(), "namespace AAA { } using namespace AAB;", Args));
202   ASSERT_LE(0, Provider.CallCount);
203   ASSERT_EQ(1, Watcher.SeenCount);
204 }
205 
206 // Check that we use the first successful TypoCorrection returned from an
207 // ExternalSemaSource.
TEST(ExternalSemaSource,ExternalTypoCorrectionOrdering)208 TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) {
209   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
210       new ExternalSemaSourceInstaller);
211   NamespaceTypoProvider First("XXX", "BBB");
212   NamespaceTypoProvider Second("AAB", "CCC");
213   NamespaceTypoProvider Third("AAB", "DDD");
214   NamespaceDiagnosticWatcher Watcher("AAB", "CCC");
215   Installer->PushSource(&First);
216   Installer->PushSource(&Second);
217   Installer->PushSource(&Third);
218   Installer->PushWatcher(&Watcher);
219   std::vector<std::string> Args(1, "-std=c++11");
220   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
221       Installer.release(), "namespace AAA { } using namespace AAB;", Args));
222   ASSERT_LE(1, First.CallCount);
223   ASSERT_LE(1, Second.CallCount);
224   ASSERT_EQ(0, Third.CallCount);
225   ASSERT_EQ(1, Watcher.SeenCount);
226 }
227 
228 // We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
229 // solve the problem.
TEST(ExternalSemaSource,TryOtherTacticsBeforeDiagnosing)230 TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {
231   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
232       new ExternalSemaSourceInstaller);
233   CompleteTypeDiagnoser Diagnoser(false);
234   Installer->PushSource(&Diagnoser);
235   std::vector<std::string> Args(1, "-std=c++11");
236   // This code hits the class template specialization/class member of a class
237   // template specialization checks in Sema::RequireCompleteTypeImpl.
238   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
239       Installer.release(),
240       "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
241       Args));
242   ASSERT_EQ(0, Diagnoser.CallCount);
243 }
244 
245 // The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
246 // true should be the last one called.
TEST(ExternalSemaSource,FirstDiagnoserTaken)247 TEST(ExternalSemaSource, FirstDiagnoserTaken) {
248   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
249       new ExternalSemaSourceInstaller);
250   CompleteTypeDiagnoser First(false);
251   CompleteTypeDiagnoser Second(true);
252   CompleteTypeDiagnoser Third(true);
253   Installer->PushSource(&First);
254   Installer->PushSource(&Second);
255   Installer->PushSource(&Third);
256   std::vector<std::string> Args(1, "-std=c++11");
257   ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(
258       Installer.release(), "class Incomplete; Incomplete IncompleteInstance;",
259       Args));
260   ASSERT_EQ(1, First.CallCount);
261   ASSERT_EQ(1, Second.CallCount);
262   ASSERT_EQ(0, Third.CallCount);
263 }
264 
265 } // anonymous namespace
266