blob: edf91c9a081dfd465884655cd727a5d0d114350e [file] [log] [blame]
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +00001//=== unittests/Sema/ExternalSemaSourceTest.cpp - ExternalSemaSource tests ===//
2//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +00006//
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
21using namespace clang;
22using namespace clang::tooling;
23
24namespace {
25
Kaelyn Uhrain2c351bb2013-08-12 22:11:14 +000026// \brief Counts the number of times MaybeDiagnoseMissingCompleteType
27// is called. Returns the result it was provided on creation.
28class CompleteTypeDiagnoser : public clang::ExternalSemaSource {
29public:
30 CompleteTypeDiagnoser(bool MockResult) : CallCount(0), Result(MockResult) {}
31
Alexander Kornienko34eb2072015-04-11 02:00:23 +000032 bool MaybeDiagnoseMissingCompleteType(SourceLocation L, QualType T) override {
Kaelyn Uhrain2c351bb2013-08-12 22:11:14 +000033 ++CallCount;
34 return Result;
35 }
36
37 int CallCount;
38 bool Result;
39};
40
Benjamin Kramerb8727332016-05-19 10:46:10 +000041/// Counts the number of typo-correcting diagnostics correcting from one name to
42/// another while still passing all diagnostics along a chain of consumers.
43class DiagnosticWatcher : public clang::DiagnosticConsumer {
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +000044 DiagnosticConsumer *Chained;
Benjamin Kramerb8727332016-05-19 10:46:10 +000045 std::string FromName;
46 std::string ToName;
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +000047
48public:
Benjamin Kramerb8727332016-05-19 10:46:10 +000049 DiagnosticWatcher(StringRef From, StringRef To)
50 : Chained(nullptr), FromName(From), ToName("'"), SeenCount(0) {
51 ToName.append(To);
52 ToName.append("'");
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +000053 }
54
Alexander Kornienko34eb2072015-04-11 02:00:23 +000055 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
56 const Diagnostic &Info) override {
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +000057 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);
Benjamin Kramerb8727332016-05-19 10:46:10 +000062 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)
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +000068 ++SeenCount;
69 }
70 }
71
Alexander Kornienko34eb2072015-04-11 02:00:23 +000072 void clear() override {
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +000073 DiagnosticConsumer::clear();
74 if (Chained)
75 Chained->clear();
76 }
77
Alexander Kornienko34eb2072015-04-11 02:00:23 +000078 bool IncludeInDiagnosticCounts() const override {
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +000079 if (Chained)
80 return Chained->IncludeInDiagnosticCounts();
81 return false;
82 }
83
Benjamin Kramerb8727332016-05-19 10:46:10 +000084 DiagnosticWatcher *Chain(DiagnosticConsumer *ToChain) {
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +000085 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.
94class NamespaceTypoProvider : public clang::ExternalSemaSource {
95 std::string CorrectFrom;
96 std::string CorrectTo;
97 Sema *CurrentSema;
98
99public:
100 NamespaceTypoProvider(StringRef From, StringRef To)
Craig Topper416fa342014-06-08 08:38:12 +0000101 : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000102
Alexander Kornienko34eb2072015-04-11 02:00:23 +0000103 void InitializeSema(Sema &S) override { CurrentSema = &S; }
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000104
Alexander Kornienko34eb2072015-04-11 02:00:23 +0000105 void ForgetSema() override { CurrentSema = nullptr; }
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000106
Alexander Kornienko34eb2072015-04-11 02:00:23 +0000107 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 {
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000112 ++CallCount;
113 if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {
Craig Topper416fa342014-06-08 08:38:12 +0000114 DeclContext *DestContext = nullptr;
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000115 ASTContext &Context = CurrentSema->getASTContext();
Craig Topper416fa342014-06-08 08:38:12 +0000116 if (SS)
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000117 DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);
Craig Topper416fa342014-06-08 08:38:12 +0000118 if (!DestContext)
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000119 DestContext = Context.getTranslationUnitDecl();
120 IdentifierInfo *ToIdent =
121 CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
122 NamespaceDecl *NewNamespace =
123 NamespaceDecl::Create(Context, DestContext, false, Typo.getBeginLoc(),
Craig Topper416fa342014-06-08 08:38:12 +0000124 Typo.getLoc(), ToIdent, nullptr);
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000125 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
Benjamin Kramerb8727332016-05-19 10:46:10 +0000136class FunctionTypoProvider : public clang::ExternalSemaSource {
137 std::string CorrectFrom;
138 std::string CorrectTo;
139 Sema *CurrentSema;
140
141public:
142 FunctionTypoProvider(StringRef From, StringRef To)
143 : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
144
145 void InitializeSema(Sema &S) override { CurrentSema = &S; }
146
147 void ForgetSema() override { CurrentSema = nullptr; }
148
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
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000179// adds a vector of ExternalSemaSources to the CompilerInstance before
180// performing semantic analysis.
181class ExternalSemaSourceInstaller : public clang::ASTFrontendAction {
Benjamin Kramerb8727332016-05-19 10:46:10 +0000182 std::vector<DiagnosticWatcher *> Watchers;
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000183 std::vector<clang::ExternalSemaSource *> Sources;
Ahmed Charlesb8984322014-03-07 20:03:18 +0000184 std::unique_ptr<DiagnosticConsumer> OwnedClient;
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000185
186protected:
Alexander Kornienko34eb2072015-04-11 02:00:23 +0000187 std::unique_ptr<clang::ASTConsumer>
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000188 CreateASTConsumer(clang::CompilerInstance &Compiler,
Alexander Kornienko34eb2072015-04-11 02:00:23 +0000189 llvm::StringRef /* dummy */) override {
Jonas Devlieghere2b3d49b2019-08-14 23:04:18 +0000190 return std::make_unique<clang::ASTConsumer>();
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000191 }
192
Alexander Kornienko34eb2072015-04-11 02:00:23 +0000193 void ExecuteAction() override {
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000194 CompilerInstance &CI = getCompilerInstance();
195 ASSERT_FALSE(CI.hasSema());
Craig Topper416fa342014-06-08 08:38:12 +0000196 CI.createSema(getTranslationUnitKind(), nullptr);
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000197 ASSERT_TRUE(CI.hasDiagnostics());
198 DiagnosticsEngine &Diagnostics = CI.getDiagnostics();
199 DiagnosticConsumer *Client = Diagnostics.getClient();
200 if (Diagnostics.ownsClient())
Alexander Kornienko41c247a2014-11-17 23:46:02 +0000201 OwnedClient = Diagnostics.takeClient();
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000202 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
213public:
214 void PushSource(clang::ExternalSemaSource *Source) {
215 Sources.push_back(Source);
216 }
217
Benjamin Kramerb8727332016-05-19 10:46:10 +0000218 void PushWatcher(DiagnosticWatcher *Watcher) { Watchers.push_back(Watcher); }
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000219};
220
Benjamin Kramerb8727332016-05-19 10:46:10 +0000221// Make sure that the DiagnosticWatcher is not miscounting.
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000222TEST(ExternalSemaSource, SanityCheck) {
Roman Lebedev497fd982018-02-27 15:54:55 +0000223 std::unique_ptr<ExternalSemaSourceInstaller> Installer(
224 new ExternalSemaSourceInstaller);
Benjamin Kramerb8727332016-05-19 10:46:10 +0000225 DiagnosticWatcher Watcher("AAB", "BBB");
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000226 Installer->PushWatcher(&Watcher);
227 std::vector<std::string> Args(1, "-std=c++11");
228 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
Roman Lebedev497fd982018-02-27 15:54:55 +0000229 Installer.release(), "namespace AAA { } using namespace AAB;", Args));
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000230 ASSERT_EQ(0, Watcher.SeenCount);
231}
232
233// Check that when we add a NamespaceTypeProvider, we use that suggestion
234// instead of the usual suggestion we would use above.
235TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) {
Roman Lebedev497fd982018-02-27 15:54:55 +0000236 std::unique_ptr<ExternalSemaSourceInstaller> Installer(
237 new ExternalSemaSourceInstaller);
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000238 NamespaceTypoProvider Provider("AAB", "BBB");
Benjamin Kramerb8727332016-05-19 10:46:10 +0000239 DiagnosticWatcher Watcher("AAB", "BBB");
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000240 Installer->PushSource(&Provider);
241 Installer->PushWatcher(&Watcher);
242 std::vector<std::string> Args(1, "-std=c++11");
243 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
Roman Lebedev497fd982018-02-27 15:54:55 +0000244 Installer.release(), "namespace AAA { } using namespace AAB;", Args));
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000245 ASSERT_LE(0, Provider.CallCount);
246 ASSERT_EQ(1, Watcher.SeenCount);
247}
248
249// Check that we use the first successful TypoCorrection returned from an
250// ExternalSemaSource.
251TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) {
Roman Lebedev497fd982018-02-27 15:54:55 +0000252 std::unique_ptr<ExternalSemaSourceInstaller> Installer(
253 new ExternalSemaSourceInstaller);
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000254 NamespaceTypoProvider First("XXX", "BBB");
255 NamespaceTypoProvider Second("AAB", "CCC");
256 NamespaceTypoProvider Third("AAB", "DDD");
Benjamin Kramerb8727332016-05-19 10:46:10 +0000257 DiagnosticWatcher Watcher("AAB", "CCC");
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000258 Installer->PushSource(&First);
259 Installer->PushSource(&Second);
260 Installer->PushSource(&Third);
261 Installer->PushWatcher(&Watcher);
262 std::vector<std::string> Args(1, "-std=c++11");
263 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
Roman Lebedev497fd982018-02-27 15:54:55 +0000264 Installer.release(), "namespace AAA { } using namespace AAB;", Args));
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000265 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
Benjamin Kramerb8727332016-05-19 10:46:10 +0000271TEST(ExternalSemaSource, ExternalDelayedTypoCorrection) {
Roman Lebedev497fd982018-02-27 15:54:55 +0000272 std::unique_ptr<ExternalSemaSourceInstaller> Installer(
273 new ExternalSemaSourceInstaller);
Benjamin Kramerb8727332016-05-19 10:46:10 +0000274 FunctionTypoProvider Provider("aaa", "bbb");
275 DiagnosticWatcher Watcher("aaa", "bbb");
276 Installer->PushSource(&Provider);
277 Installer->PushWatcher(&Watcher);
278 std::vector<std::string> Args(1, "-std=c++11");
279 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
Roman Lebedev497fd982018-02-27 15:54:55 +0000280 Installer.release(), "namespace AAA { } void foo() { AAA::aaa(); }",
Benjamin Kramerb8727332016-05-19 10:46:10 +0000281 Args));
282 ASSERT_LE(0, Provider.CallCount);
283 ASSERT_EQ(1, Watcher.SeenCount);
284}
285
Kaelyn Uhrain2c351bb2013-08-12 22:11:14 +0000286// We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
287// solve the problem.
288TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {
Roman Lebedev497fd982018-02-27 15:54:55 +0000289 std::unique_ptr<ExternalSemaSourceInstaller> Installer(
290 new ExternalSemaSourceInstaller);
Kaelyn Uhrain2c351bb2013-08-12 22:11:14 +0000291 CompleteTypeDiagnoser Diagnoser(false);
292 Installer->PushSource(&Diagnoser);
293 std::vector<std::string> Args(1, "-std=c++11");
294 // This code hits the class template specialization/class member of a class
295 // template specialization checks in Sema::RequireCompleteTypeImpl.
296 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
Roman Lebedev497fd982018-02-27 15:54:55 +0000297 Installer.release(),
Kaelyn Uhrain2c351bb2013-08-12 22:11:14 +0000298 "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
299 Args));
300 ASSERT_EQ(0, Diagnoser.CallCount);
301}
302
303// The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
304// true should be the last one called.
305TEST(ExternalSemaSource, FirstDiagnoserTaken) {
Roman Lebedev497fd982018-02-27 15:54:55 +0000306 std::unique_ptr<ExternalSemaSourceInstaller> Installer(
307 new ExternalSemaSourceInstaller);
Kaelyn Uhrain2c351bb2013-08-12 22:11:14 +0000308 CompleteTypeDiagnoser First(false);
309 CompleteTypeDiagnoser Second(true);
310 CompleteTypeDiagnoser Third(true);
311 Installer->PushSource(&First);
312 Installer->PushSource(&Second);
313 Installer->PushSource(&Third);
314 std::vector<std::string> Args(1, "-std=c++11");
315 ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(
Roman Lebedev497fd982018-02-27 15:54:55 +0000316 Installer.release(), "class Incomplete; Incomplete IncompleteInstance;",
Kaelyn Uhrain2c351bb2013-08-12 22:11:14 +0000317 Args));
318 ASSERT_EQ(1, First.CallCount);
319 ASSERT_EQ(1, Second.CallCount);
320 ASSERT_EQ(0, Third.CallCount);
321}
322
Kaelyn Uhrain4ebf5762013-08-12 19:57:06 +0000323} // anonymous namespace