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