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, false);
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 using llvm::makeIntrusiveRefCnt
;
224 // Make sure that the DiagnosticWatcher is not miscounting.
225 TEST(ExternalSemaSource
, DiagCheck
) {
226 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
227 DiagnosticWatcher
Watcher("AAB", "BBB");
228 Installer
->PushWatcher(&Watcher
);
229 std::vector
<std::string
> Args(1, "-std=c++11");
230 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
231 std::move(Installer
), "namespace AAA { } using namespace AAB;", Args
));
232 ASSERT_EQ(0, Watcher
.SeenCount
);
235 // Check that when we add a NamespaceTypeProvider, we use that suggestion
236 // instead of the usual suggestion we would use above.
237 TEST(ExternalSemaSource
, ExternalTypoCorrectionPrioritized
) {
238 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
239 auto Provider
= makeIntrusiveRefCnt
<NamespaceTypoProvider
>("AAB", "BBB");
240 DiagnosticWatcher
Watcher("AAB", "BBB");
241 Installer
->PushSource(Provider
.get());
242 Installer
->PushWatcher(&Watcher
);
243 std::vector
<std::string
> Args(1, "-std=c++11");
244 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
245 std::move(Installer
), "namespace AAA { } using namespace AAB;", Args
));
246 ASSERT_LE(0, Provider
->CallCount
);
247 ASSERT_EQ(1, Watcher
.SeenCount
);
250 // Check that we use the first successful TypoCorrection returned from an
251 // ExternalSemaSource.
252 TEST(ExternalSemaSource
, ExternalTypoCorrectionOrdering
) {
253 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
254 auto First
= makeIntrusiveRefCnt
<NamespaceTypoProvider
>("XXX", "BBB");
255 auto Second
= makeIntrusiveRefCnt
<NamespaceTypoProvider
>("AAB", "CCC");
256 auto Third
= makeIntrusiveRefCnt
<NamespaceTypoProvider
>("AAB", "DDD");
257 DiagnosticWatcher
Watcher("AAB", "CCC");
258 Installer
->PushSource(First
.get());
259 Installer
->PushSource(Second
.get());
260 Installer
->PushSource(Third
.get());
261 Installer
->PushWatcher(&Watcher
);
262 std::vector
<std::string
> Args(1, "-std=c++11");
263 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
264 std::move(Installer
), "namespace AAA { } using namespace AAB;", Args
));
265 ASSERT_LE(1, First
->CallCount
);
266 ASSERT_LE(1, Second
->CallCount
);
267 ASSERT_EQ(0, Third
->CallCount
);
268 ASSERT_EQ(1, Watcher
.SeenCount
);
271 TEST(ExternalSemaSource
, ExternalDelayedTypoCorrection
) {
272 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
273 auto Provider
= makeIntrusiveRefCnt
<FunctionTypoProvider
>("aaa", "bbb");
274 DiagnosticWatcher
Watcher("aaa", "bbb");
275 Installer
->PushSource(Provider
.get());
276 Installer
->PushWatcher(&Watcher
);
277 std::vector
<std::string
> Args(1, "-std=c++11");
278 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
279 std::move(Installer
), "namespace AAA { } void foo() { AAA::aaa(); }",
281 ASSERT_LE(0, Provider
->CallCount
);
282 ASSERT_EQ(1, Watcher
.SeenCount
);
285 // We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
286 // solve the problem.
287 TEST(ExternalSemaSource
, TryOtherTacticsBeforeDiagnosing
) {
288 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
289 auto Diagnoser
= makeIntrusiveRefCnt
<CompleteTypeDiagnoser
>(false);
290 Installer
->PushSource(Diagnoser
.get());
291 std::vector
<std::string
> Args(1, "-std=c++11");
292 // This code hits the class template specialization/class member of a class
293 // template specialization checks in Sema::RequireCompleteTypeImpl.
294 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
295 std::move(Installer
),
296 "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
298 ASSERT_EQ(0, Diagnoser
->CallCount
);
301 // The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
302 // true should be the last one called.
303 TEST(ExternalSemaSource
, FirstDiagnoserTaken
) {
304 auto Installer
= std::make_unique
<ExternalSemaSourceInstaller
>();
305 auto First
= makeIntrusiveRefCnt
<CompleteTypeDiagnoser
>(false);
306 auto Second
= makeIntrusiveRefCnt
<CompleteTypeDiagnoser
>(true);
307 auto Third
= makeIntrusiveRefCnt
<CompleteTypeDiagnoser
>(true);
308 Installer
->PushSource(First
.get());
309 Installer
->PushSource(Second
.get());
310 Installer
->PushSource(Third
.get());
311 std::vector
<std::string
> Args(1, "-std=c++11");
312 ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(
313 std::move(Installer
), "class Incomplete; Incomplete IncompleteInstance;",
315 ASSERT_EQ(1, First
->CallCount
);
316 ASSERT_EQ(1, Second
->CallCount
);
317 ASSERT_EQ(0, Third
->CallCount
);
320 } // anonymous namespace