1 //=== unittests/Sema/ExternalSemaSourceTest.cpp - ExternalSemaSource tests ===//
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
7 //===----------------------------------------------------------------------===//
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"
21 using namespace clang
;
22 using namespace clang::tooling
;
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
{
30 CompleteTypeDiagnoser(bool MockResult
) : CallCount(0), Result(MockResult
) {}
32 bool MaybeDiagnoseMissingCompleteType(SourceLocation L
, QualType T
) override
{
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
;
49 DiagnosticWatcher(StringRef From
, StringRef To
)
50 : Chained(nullptr), FromName(From
), ToName("'"), SeenCount(0) {
51 ToName
.append(std::string(To
));
55 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel
,
56 const Diagnostic
&Info
) override
{
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
)
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
)
72 void clear() override
{
73 DiagnosticConsumer::clear();
78 bool IncludeInDiagnosticCounts() const override
{
80 return Chained
->IncludeInDiagnosticCounts();
84 DiagnosticWatcher
*Chain(DiagnosticConsumer
*ToChain
) {
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
;
100 NamespaceTypoProvider(StringRef From
, StringRef To
)
101 : CorrectFrom(From
), CorrectTo(To
), CurrentSema(nullptr), CallCount(0) {}
103 void InitializeSema(Sema
&S
) override
{ CurrentSema
= &S
; }
105 void ForgetSema() override
{ CurrentSema
= nullptr; }
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
{
113 if (CurrentSema
&& Typo
.getName().getAsString() == CorrectFrom
) {
114 DeclContext
*DestContext
= nullptr;
115 ASTContext
&Context
= CurrentSema
->getASTContext();
117 DestContext
= CurrentSema
->computeDeclContext(*SS
, EnteringContext
);
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
);
130 return TypoCorrection();
136 class FunctionTypoProvider
: public clang::ExternalSemaSource
{
137 std::string CorrectFrom
;
138 std::string CorrectTo
;
142 FunctionTypoProvider(StringRef From
, StringRef To
)
143 : CorrectFrom(From
), CorrectTo(To
), CurrentSema(nullptr), CallCount(0) {}
145 void InitializeSema(Sema
&S
) override
{ CurrentSema
= &S
; }
147 void ForgetSema() override
{ CurrentSema
= nullptr; }
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
{
155 if (CurrentSema
&& Typo
.getName().getAsString() == CorrectFrom
) {
156 DeclContext
*DestContext
= nullptr;
157 ASTContext
&Context
= CurrentSema
->getASTContext();
159 DestContext
= CurrentSema
->computeDeclContext(*SS
, EnteringContext
);
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 /*UsesFPIntrin*/ false);
168 DestContext
->addDecl(NewFunction
);
169 TypoCorrection
Correction(ToIdent
);
170 Correction
.addCorrectionDecl(NewFunction
);
173 return TypoCorrection();
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
;
188 std::unique_ptr
<clang::ASTConsumer
>
189 CreateASTConsumer(clang::CompilerInstance
&Compiler
,
190 llvm::StringRef
/* dummy */) override
{
191 return std::make_unique
<clang::ASTConsumer
>();
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
]);
210 ParseAST(CI
.getSema(), CI
.getFrontendOpts().ShowStats
,
211 CI
.getFrontendOpts().SkipFunctionBodies
);
215 void PushSource(clang::ExternalSemaSource
*Source
) {
216 Sources
.push_back(Source
);
219 void PushWatcher(DiagnosticWatcher
*Watcher
) { Watchers
.push_back(Watcher
); }
222 // Make sure that the DiagnosticWatcher is not miscounting.
223 TEST(ExternalSemaSource
, DiagCheck
) {
224 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
225 DiagnosticWatcher
Watcher("AAB", "BBB");
226 Installer
->PushWatcher(&Watcher
);
227 std::vector
<std::string
> Args(1, "-std=c++11");
228 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
229 std::move(Installer
), "namespace AAA { } using namespace AAB;", Args
));
230 ASSERT_EQ(0, Watcher
.SeenCount
);
233 // Check that when we add a NamespaceTypeProvider, we use that suggestion
234 // instead of the usual suggestion we would use above.
235 TEST(ExternalSemaSource
, ExternalTypoCorrectionPrioritized
) {
236 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
237 NamespaceTypoProvider
Provider("AAB", "BBB");
238 DiagnosticWatcher
Watcher("AAB", "BBB");
239 Installer
->PushSource(&Provider
);
240 Installer
->PushWatcher(&Watcher
);
241 std::vector
<std::string
> Args(1, "-std=c++11");
242 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
243 std::move(Installer
), "namespace AAA { } using namespace AAB;", Args
));
244 ASSERT_LE(0, Provider
.CallCount
);
245 ASSERT_EQ(1, Watcher
.SeenCount
);
248 // Check that we use the first successful TypoCorrection returned from an
249 // ExternalSemaSource.
250 TEST(ExternalSemaSource
, ExternalTypoCorrectionOrdering
) {
251 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
252 NamespaceTypoProvider
First("XXX", "BBB");
253 NamespaceTypoProvider
Second("AAB", "CCC");
254 NamespaceTypoProvider
Third("AAB", "DDD");
255 DiagnosticWatcher
Watcher("AAB", "CCC");
256 Installer
->PushSource(&First
);
257 Installer
->PushSource(&Second
);
258 Installer
->PushSource(&Third
);
259 Installer
->PushWatcher(&Watcher
);
260 std::vector
<std::string
> Args(1, "-std=c++11");
261 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
262 std::move(Installer
), "namespace AAA { } using namespace AAB;", Args
));
263 ASSERT_LE(1, First
.CallCount
);
264 ASSERT_LE(1, Second
.CallCount
);
265 ASSERT_EQ(0, Third
.CallCount
);
266 ASSERT_EQ(1, Watcher
.SeenCount
);
269 TEST(ExternalSemaSource
, ExternalDelayedTypoCorrection
) {
270 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
271 FunctionTypoProvider
Provider("aaa", "bbb");
272 DiagnosticWatcher
Watcher("aaa", "bbb");
273 Installer
->PushSource(&Provider
);
274 Installer
->PushWatcher(&Watcher
);
275 std::vector
<std::string
> Args(1, "-std=c++11");
276 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
277 std::move(Installer
), "namespace AAA { } void foo() { AAA::aaa(); }",
279 ASSERT_LE(0, Provider
.CallCount
);
280 ASSERT_EQ(1, Watcher
.SeenCount
);
283 // We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
284 // solve the problem.
285 TEST(ExternalSemaSource
, TryOtherTacticsBeforeDiagnosing
) {
286 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
287 CompleteTypeDiagnoser
Diagnoser(false);
288 Installer
->PushSource(&Diagnoser
);
289 std::vector
<std::string
> Args(1, "-std=c++11");
290 // This code hits the class template specialization/class member of a class
291 // template specialization checks in Sema::RequireCompleteTypeImpl.
292 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
293 std::move(Installer
),
294 "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
296 ASSERT_EQ(0, Diagnoser
.CallCount
);
299 // The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
300 // true should be the last one called.
301 TEST(ExternalSemaSource
, FirstDiagnoserTaken
) {
302 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
303 CompleteTypeDiagnoser
First(false);
304 CompleteTypeDiagnoser
Second(true);
305 CompleteTypeDiagnoser
Third(true);
306 Installer
->PushSource(&First
);
307 Installer
->PushSource(&Second
);
308 Installer
->PushSource(&Third
);
309 std::vector
<std::string
> Args(1, "-std=c++11");
310 ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(
311 std::move(Installer
), "class Incomplete; Incomplete IncompleteInstance;",
313 ASSERT_EQ(1, First
.CallCount
);
314 ASSERT_EQ(1, Second
.CallCount
);
315 ASSERT_EQ(0, Third
.CallCount
);
318 } // anonymous namespace