Clang Project

clang_source_code/unittests/Sema/ExternalSemaSourceTest.cpp
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 LQualType 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 FromStringRef To)
50      : Chained(nullptr), FromName(From), ToName("'"), SeenCount(0) {
51    ToName.append(To);
52    ToName.append("'");
53  }
54
55  void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
56                        const Diagnostic &Info) override {
57    if (Chained)
58      Chained->HandleDiagnostic(DiagLevelInfo);
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)
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)
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 FromStringRef 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 &Typoint LookupKind,
108                             Scope *SCXXScopeSpec *SS,
109                             CorrectionCandidateCallback &CCC,
110                             DeclContext *MemberContextbool 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(*SSEnteringContext);
118      if (!DestContext)
119        DestContext = Context.getTranslationUnitDecl();
120      IdentifierInfo *ToIdent =
121          CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
122      NamespaceDecl *NewNamespace =
123          NamespaceDecl::Create(ContextDestContextfalseTypo.getBeginLoc(),
124                                Typo.getLoc(), ToIdentnullptr);
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 FromStringRef 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 &Typoint LookupKind,
150                             Scope *SCXXScopeSpec *SS,
151                             CorrectionCandidateCallback &CCC,
152                             DeclContext *MemberContextbool 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(*SSEnteringContext);
160      if (!DestContext)
161        DestContext = Context.getTranslationUnitDecl();
162      IdentifierInfo *ToIdent =
163          CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
164      auto *NewFunction = FunctionDecl::Create(
165          ContextDestContextSourceLocation(), SourceLocation(), ToIdent,
166          Context.getFunctionType(Context.VoidTy, {}, {}), nullptrSC_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
179// adds a vector of ExternalSemaSources to the CompilerInstance before
180// performing semantic analysis.
181class ExternalSemaSourceInstaller : public clang::ASTFrontendAction {
182  std::vector<DiagnosticWatcher *> Watchers;
183  std::vector<clang::ExternalSemaSource *> Sources;
184  std::unique_ptr<DiagnosticConsumer> OwnedClient;
185
186protected:
187  std::unique_ptr<clang::ASTConsumer>
188  CreateASTConsumer(clang::CompilerInstance &Compiler,
189                    llvm::StringRef /* dummy */) override {
190    return llvm::make_unique<clang::ASTConsumer>();
191  }
192
193  void ExecuteAction() override {
194    CompilerInstance &CI = getCompilerInstance();
195    ASSERT_FALSE(CI.hasSema());
196    CI.createSema(getTranslationUnitKind(), nullptr);
197    ASSERT_TRUE(CI.hasDiagnostics());
198    DiagnosticsEngine &Diagnostics = CI.getDiagnostics();
199    DiagnosticConsumer *Client = Diagnostics.getClient();
200    if (Diagnostics.ownsClient())
201      OwnedClient = Diagnostics.takeClient();
202    for (size_t I = 0, E = Watchers.size(); I < E; ++I)
203      Client = Watchers[I]->Chain(Client);
204    Diagnostics.setClient(Clientfalse);
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
218  void PushWatcher(DiagnosticWatcher *Watcher) { Watchers.push_back(Watcher); }
219};
220
221// Make sure that the DiagnosticWatcher is not miscounting.
222TEST(ExternalSemaSource, SanityCheck) {
223  std::unique_ptr<ExternalSemaSourceInstaller> Installer(
224      new 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      Installer.release(), "namespace AAA { } using namespace AAB;", Args));
230  ASSERT_EQ(0Watcher.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) {
236  std::unique_ptr<ExternalSemaSourceInstaller> Installer(
237      new ExternalSemaSourceInstaller);
238  NamespaceTypoProvider Provider("AAB""BBB");
239  DiagnosticWatcher Watcher("AAB""BBB");
240  Installer->PushSource(&Provider);
241  Installer->PushWatcher(&Watcher);
242  std::vector<std::string> Args(1"-std=c++11");
243  ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
244      Installer.release(), "namespace AAA { } using namespace AAB;", Args));
245  ASSERT_LE(0Provider.CallCount);
246  ASSERT_EQ(1Watcher.SeenCount);
247}
248
249// Check that we use the first successful TypoCorrection returned from an
250// ExternalSemaSource.
251TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) {
252  std::unique_ptr<ExternalSemaSourceInstaller> Installer(
253      new ExternalSemaSourceInstaller);
254  NamespaceTypoProvider First("XXX""BBB");
255  NamespaceTypoProvider Second("AAB""CCC");
256  NamespaceTypoProvider Third("AAB""DDD");
257  DiagnosticWatcher Watcher("AAB""CCC");
258  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(
264      Installer.release(), "namespace AAA { } using namespace AAB;", Args));
265  ASSERT_LE(1First.CallCount);
266  ASSERT_LE(1Second.CallCount);
267  ASSERT_EQ(0Third.CallCount);
268  ASSERT_EQ(1Watcher.SeenCount);
269}
270
271TEST(ExternalSemaSource, ExternalDelayedTypoCorrection) {
272  std::unique_ptr<ExternalSemaSourceInstaller> Installer(
273      new ExternalSemaSourceInstaller);
274  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(
280      Installer.release(), "namespace AAA { } void foo() { AAA::aaa(); }",
281      Args));
282  ASSERT_LE(0Provider.CallCount);
283  ASSERT_EQ(1Watcher.SeenCount);
284}
285
286// We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
287// solve the problem.
288TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {
289  std::unique_ptr<ExternalSemaSourceInstaller> Installer(
290      new ExternalSemaSourceInstaller);
291  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(
297      Installer.release(),
298      "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
299      Args));
300  ASSERT_EQ(0Diagnoser.CallCount);
301}
302
303// The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
304// true should be the last one called.
305TEST(ExternalSemaSource, FirstDiagnoserTaken) {
306  std::unique_ptr<ExternalSemaSourceInstaller> Installer(
307      new ExternalSemaSourceInstaller);
308  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(
316      Installer.release(), "class Incomplete; Incomplete IncompleteInstance;",
317      Args));
318  ASSERT_EQ(1First.CallCount);
319  ASSERT_EQ(1Second.CallCount);
320  ASSERT_EQ(0Third.CallCount);
321}
322
323// anonymous namespace
324