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
21using namespace clang;
22using namespace clang::tooling;
23
24namespace {
25
26// \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
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.
43class DiagnosticWatcher : public clang::DiagnosticConsumer {
44 DiagnosticConsumer *Chained;
45 std::string FromName;
46 std::string ToName;
47
48public:
49 DiagnosticWatcher(StringRef From, StringRef To)
50 : Chained(nullptr), FromName(From), ToName("'"), SeenCount(0) {
51 ToName.append(str: std::string(To));
52 ToName.append(s: "'");
53 }
54
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(Idx: 0);
61 const std::string &CorrectedQuotedStr = Info.getArgStdStr(Idx: 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(P: Info.getRawArg(Idx: 0));
66 const std::string &CorrectedQuotedStr = Info.getArgStdStr(Idx: 3);
67 if (Ident.getAsString() == FromName && CorrectedQuotedStr == ToName)
68 ++SeenCount;
69 }
70 }
71
72 void clear() override {
73 DiagnosticConsumer::clear();
74 if (Chained)
75 Chained->clear();
76 }
77
78 bool IncludeInDiagnosticCounts() const override {
79 if (Chained)
80 return Chained->IncludeInDiagnosticCounts();
81 return false;
82 }
83
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.
94class NamespaceTypoProvider : public clang::ExternalSemaSource {
95 std::string CorrectFrom;
96 std::string CorrectTo;
97 Sema *CurrentSema;
98
99public:
100 NamespaceTypoProvider(StringRef From, StringRef To)
101 : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
102
103 void InitializeSema(Sema &S) override { CurrentSema = &S; }
104
105 void ForgetSema() override { CurrentSema = nullptr; }
106
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: *SS, EnteringContext);
118 if (!DestContext)
119 DestContext = Context.getTranslationUnitDecl();
120 IdentifierInfo *ToIdent =
121 CurrentSema->getPreprocessor().getIdentifierInfo(Name: CorrectTo);
122 NamespaceDecl *NewNamespace =
123 NamespaceDecl::Create(C&: Context, DC: DestContext, Inline: false, StartLoc: Typo.getBeginLoc(),
124 IdLoc: Typo.getLoc(), Id: ToIdent, PrevDecl: nullptr, Nested: false);
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
136class 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: *SS, EnteringContext);
160 if (!DestContext)
161 DestContext = Context.getTranslationUnitDecl();
162 IdentifierInfo *ToIdent =
163 CurrentSema->getPreprocessor().getIdentifierInfo(Name: CorrectTo);
164 auto *NewFunction = FunctionDecl::Create(
165 Context, DestContext, SourceLocation(), SourceLocation(), ToIdent,
166 Context.getFunctionType(ResultTy: Context.VoidTy, Args: {}, EPI: {}), nullptr, SC_Static,
167 /*UsesFPIntrin*/ false);
168 DestContext->addDecl(D: NewFunction);
169 TypoCorrection Correction(ToIdent);
170 Correction.addCorrectionDecl(CDecl: 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.
182class ExternalSemaSourceInstaller : public clang::ASTFrontendAction {
183 std::vector<DiagnosticWatcher *> Watchers;
184 std::vector<clang::ExternalSemaSource *> Sources;
185 std::unique_ptr<DiagnosticConsumer> OwnedClient;
186
187protected:
188 std::unique_ptr<clang::ASTConsumer>
189 CreateASTConsumer(clang::CompilerInstance &Compiler,
190 llvm::StringRef /* dummy */) override {
191 return std::make_unique<clang::ASTConsumer>();
192 }
193
194 void ExecuteAction() override {
195 CompilerInstance &CI = getCompilerInstance();
196 ASSERT_FALSE(CI.hasSema());
197 CI.createSema(TUKind: getTranslationUnitKind(), CompletionConsumer: 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(ToChain: Client);
205 Diagnostics.setClient(client: Client, ShouldOwnClient: false);
206 for (size_t I = 0, E = Sources.size(); I < E; ++I) {
207 Sources[I]->InitializeSema(S&: CI.getSema());
208 CI.getSema().addExternalSource(E: Sources[I]);
209 }
210 ParseAST(S&: CI.getSema(), PrintStats: CI.getFrontendOpts().ShowStats,
211 SkipFunctionBodies: CI.getFrontendOpts().SkipFunctionBodies);
212 }
213
214public:
215 void PushSource(clang::ExternalSemaSource *Source) {
216 Sources.push_back(x: Source);
217 }
218
219 void PushWatcher(DiagnosticWatcher *Watcher) { Watchers.push_back(x: Watcher); }
220};
221
222using llvm::makeIntrusiveRefCnt;
223
224// Make sure that the DiagnosticWatcher is not miscounting.
225TEST(ExternalSemaSource, DiagCheck) {
226 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
227 DiagnosticWatcher Watcher("AAB", "BBB");
228 Installer->PushWatcher(Watcher: &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);
233}
234
235// Check that when we add a NamespaceTypeProvider, we use that suggestion
236// instead of the usual suggestion we would use above.
237TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) {
238 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
239 auto Provider = makeIntrusiveRefCnt<NamespaceTypoProvider>(A: "AAB", A: "BBB");
240 DiagnosticWatcher Watcher("AAB", "BBB");
241 Installer->PushSource(Source: Provider.get());
242 Installer->PushWatcher(Watcher: &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);
248}
249
250// Check that we use the first successful TypoCorrection returned from an
251// ExternalSemaSource.
252TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) {
253 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
254 auto First = makeIntrusiveRefCnt<NamespaceTypoProvider>(A: "XXX", A: "BBB");
255 auto Second = makeIntrusiveRefCnt<NamespaceTypoProvider>(A: "AAB", A: "CCC");
256 auto Third = makeIntrusiveRefCnt<NamespaceTypoProvider>(A: "AAB", A: "DDD");
257 DiagnosticWatcher Watcher("AAB", "CCC");
258 Installer->PushSource(Source: First.get());
259 Installer->PushSource(Source: Second.get());
260 Installer->PushSource(Source: Third.get());
261 Installer->PushWatcher(Watcher: &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);
269}
270
271TEST(ExternalSemaSource, ExternalDelayedTypoCorrection) {
272 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
273 auto Provider = makeIntrusiveRefCnt<FunctionTypoProvider>(A: "aaa", A: "bbb");
274 DiagnosticWatcher Watcher("aaa", "bbb");
275 Installer->PushSource(Source: Provider.get());
276 Installer->PushWatcher(Watcher: &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(); }",
280 Args));
281 ASSERT_LE(0, Provider->CallCount);
282 ASSERT_EQ(1, Watcher.SeenCount);
283}
284
285// We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
286// solve the problem.
287TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {
288 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
289 auto Diagnoser = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(A: false);
290 Installer->PushSource(Source: 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;",
297 Args));
298 ASSERT_EQ(0, Diagnoser->CallCount);
299}
300
301// The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
302// true should be the last one called.
303TEST(ExternalSemaSource, FirstDiagnoserTaken) {
304 auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
305 auto First = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(A: false);
306 auto Second = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(A: true);
307 auto Third = makeIntrusiveRefCnt<CompleteTypeDiagnoser>(A: true);
308 Installer->PushSource(Source: First.get());
309 Installer->PushSource(Source: Second.get());
310 Installer->PushSource(Source: 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;",
314 Args));
315 ASSERT_EQ(1, First->CallCount);
316 ASSERT_EQ(1, Second->CallCount);
317 ASSERT_EQ(0, Third->CallCount);
318}
319
320} // anonymous namespace
321

source code of clang/unittests/Sema/ExternalSemaSourceTest.cpp