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: |
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. |
43 | class DiagnosticWatcher : public clang::DiagnosticConsumer { |
44 | DiagnosticConsumer *Chained; |
45 | std::string FromName; |
46 | std::string ToName; |
47 | |
48 | public: |
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. |
94 | class NamespaceTypoProvider : public clang::ExternalSemaSource { |
95 | std::string CorrectFrom; |
96 | std::string CorrectTo; |
97 | Sema *CurrentSema; |
98 | |
99 | public: |
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 | |
136 | class FunctionTypoProvider : public clang::ExternalSemaSource { |
137 | std::string CorrectFrom; |
138 | std::string CorrectTo; |
139 | Sema *CurrentSema; |
140 | |
141 | public: |
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. |
182 | class ExternalSemaSourceInstaller : public clang::ASTFrontendAction { |
183 | std::vector<DiagnosticWatcher *> Watchers; |
184 | std::vector<clang::ExternalSemaSource *> Sources; |
185 | std::unique_ptr<DiagnosticConsumer> OwnedClient; |
186 | |
187 | protected: |
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 | |
214 | public: |
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 | |
222 | using llvm::makeIntrusiveRefCnt; |
223 | |
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: &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. |
237 | TEST(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. |
252 | TEST(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 | |
271 | TEST(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. |
287 | TEST(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. |
303 | TEST(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 | |