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);
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 DestContext->addDecl(NewFunction);
168 TypoCorrection Correction(ToIdent);
169 Correction.addCorrectionDecl(NewFunction);
170 return Correction;
171 }
172 return TypoCorrection();
173 }
174
175 int CallCount;
176 };
177
178 // \brief Chains together a vector of DiagnosticWatchers and
179 // adds a vector of ExternalSemaSources to the CompilerInstance before
180 // performing semantic analysis.
181 class ExternalSemaSourceInstaller : public clang::ASTFrontendAction {
182 std::vector<DiagnosticWatcher *> Watchers;
183 std::vector<clang::ExternalSemaSource *> Sources;
184 std::unique_ptr<DiagnosticConsumer> OwnedClient;
185
186 protected:
187 std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance & Compiler,llvm::StringRef)188 CreateASTConsumer(clang::CompilerInstance &Compiler,
189 llvm::StringRef /* dummy */) override {
190 return std::make_unique<clang::ASTConsumer>();
191 }
192
ExecuteAction()193 void ExecuteAction() override {
194 CompilerInstance &CI = getCompilerInstance();
195 ASSERT_FALSE(CI.hasSema());
196 CI.createSema(getTranslationUnitKind(), nullptr);
197 ASSERT_TRUE(CI.hasDiagnostics());
198 DiagnosticsEngine &Diagnostics = CI.getDiagnostics();
199 DiagnosticConsumer *Client = Diagnostics.getClient();
200 if (Diagnostics.ownsClient())
201 OwnedClient = Diagnostics.takeClient();
202 for (size_t I = 0, E = Watchers.size(); I < E; ++I)
203 Client = Watchers[I]->Chain(Client);
204 Diagnostics.setClient(Client, false);
205 for (size_t I = 0, E = Sources.size(); I < E; ++I) {
206 Sources[I]->InitializeSema(CI.getSema());
207 CI.getSema().addExternalSource(Sources[I]);
208 }
209 ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats,
210 CI.getFrontendOpts().SkipFunctionBodies);
211 }
212
213 public:
PushSource(clang::ExternalSemaSource * Source)214 void PushSource(clang::ExternalSemaSource *Source) {
215 Sources.push_back(Source);
216 }
217
PushWatcher(DiagnosticWatcher * Watcher)218 void PushWatcher(DiagnosticWatcher *Watcher) { Watchers.push_back(Watcher); }
219 };
220
221 // Make sure that the DiagnosticWatcher is not miscounting.
TEST(ExternalSemaSource,SanityCheck)222 TEST(ExternalSemaSource, SanityCheck) {
223 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
224 DiagnosticWatcher Watcher("AAB", "BBB");
225 Installer->PushWatcher(&Watcher);
226 std::vector<std::string> Args(1, "-std=c++11");
227 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
228 std::move(Installer), "namespace AAA { } using namespace AAB;", Args));
229 ASSERT_EQ(0, Watcher.SeenCount);
230 }
231
232 // Check that when we add a NamespaceTypeProvider, we use that suggestion
233 // instead of the usual suggestion we would use above.
TEST(ExternalSemaSource,ExternalTypoCorrectionPrioritized)234 TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) {
235 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
236 NamespaceTypoProvider Provider("AAB", "BBB");
237 DiagnosticWatcher Watcher("AAB", "BBB");
238 Installer->PushSource(&Provider);
239 Installer->PushWatcher(&Watcher);
240 std::vector<std::string> Args(1, "-std=c++11");
241 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
242 std::move(Installer), "namespace AAA { } using namespace AAB;", Args));
243 ASSERT_LE(0, Provider.CallCount);
244 ASSERT_EQ(1, Watcher.SeenCount);
245 }
246
247 // Check that we use the first successful TypoCorrection returned from an
248 // ExternalSemaSource.
TEST(ExternalSemaSource,ExternalTypoCorrectionOrdering)249 TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) {
250 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
251 NamespaceTypoProvider First("XXX", "BBB");
252 NamespaceTypoProvider Second("AAB", "CCC");
253 NamespaceTypoProvider Third("AAB", "DDD");
254 DiagnosticWatcher Watcher("AAB", "CCC");
255 Installer->PushSource(&First);
256 Installer->PushSource(&Second);
257 Installer->PushSource(&Third);
258 Installer->PushWatcher(&Watcher);
259 std::vector<std::string> Args(1, "-std=c++11");
260 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
261 std::move(Installer), "namespace AAA { } using namespace AAB;", Args));
262 ASSERT_LE(1, First.CallCount);
263 ASSERT_LE(1, Second.CallCount);
264 ASSERT_EQ(0, Third.CallCount);
265 ASSERT_EQ(1, Watcher.SeenCount);
266 }
267
TEST(ExternalSemaSource,ExternalDelayedTypoCorrection)268 TEST(ExternalSemaSource, ExternalDelayedTypoCorrection) {
269 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
270 FunctionTypoProvider Provider("aaa", "bbb");
271 DiagnosticWatcher Watcher("aaa", "bbb");
272 Installer->PushSource(&Provider);
273 Installer->PushWatcher(&Watcher);
274 std::vector<std::string> Args(1, "-std=c++11");
275 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
276 std::move(Installer), "namespace AAA { } void foo() { AAA::aaa(); }",
277 Args));
278 ASSERT_LE(0, Provider.CallCount);
279 ASSERT_EQ(1, Watcher.SeenCount);
280 }
281
282 // We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
283 // solve the problem.
TEST(ExternalSemaSource,TryOtherTacticsBeforeDiagnosing)284 TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {
285 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
286 CompleteTypeDiagnoser Diagnoser(false);
287 Installer->PushSource(&Diagnoser);
288 std::vector<std::string> Args(1, "-std=c++11");
289 // This code hits the class template specialization/class member of a class
290 // template specialization checks in Sema::RequireCompleteTypeImpl.
291 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
292 std::move(Installer),
293 "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
294 Args));
295 ASSERT_EQ(0, Diagnoser.CallCount);
296 }
297
298 // The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
299 // true should be the last one called.
TEST(ExternalSemaSource,FirstDiagnoserTaken)300 TEST(ExternalSemaSource, FirstDiagnoserTaken) {
301 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
302 CompleteTypeDiagnoser First(false);
303 CompleteTypeDiagnoser Second(true);
304 CompleteTypeDiagnoser Third(true);
305 Installer->PushSource(&First);
306 Installer->PushSource(&Second);
307 Installer->PushSource(&Third);
308 std::vector<std::string> Args(1, "-std=c++11");
309 ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(
310 std::move(Installer), "class Incomplete; Incomplete IncompleteInstance;",
311 Args));
312 ASSERT_EQ(1, First.CallCount);
313 ASSERT_EQ(1, Second.CallCount);
314 ASSERT_EQ(0, Third.CallCount);
315 }
316
317 } // anonymous namespace
318