1//=== unittests/Sema/ExternalSemaSourceTest.cpp - ExternalSemaSource tests ===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "clang/AST/ASTConsumer.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/Frontend/CompilerInstance.h"
13#include "clang/Lex/Preprocessor.h"
14#include "clang/Parse/ParseAST.h"
15#include "clang/Sema/ExternalSemaSource.h"
16#include "clang/Sema/Sema.h"
17#include "clang/Sema/SemaDiagnostic.h"
18#include "clang/Sema/TypoCorrection.h"
19#include "clang/Tooling/Tooling.h"
20#include "gtest/gtest.h"
21
22using namespace clang;
23using namespace clang::tooling;
24
25namespace {
26
27// \brief Counts the number of times MaybeDiagnoseMissingCompleteType
28// is called. Returns the result it was provided on creation.
29class CompleteTypeDiagnoser : public clang::ExternalSemaSource {
30public:
31  CompleteTypeDiagnoser(bool MockResult) : CallCount(0), Result(MockResult) {}
32
33  virtual bool MaybeDiagnoseMissingCompleteType(SourceLocation L, QualType T) {
34    ++CallCount;
35    return Result;
36  }
37
38  int CallCount;
39  bool Result;
40};
41
42// \brief Counts the number of err_using_directive_member_suggest diagnostics
43// correcting from one namespace to another while still passing all diagnostics
44// along a chain of consumers.
45class NamespaceDiagnosticWatcher : public clang::DiagnosticConsumer {
46  DiagnosticConsumer *Chained;
47  std::string FromNS;
48  std::string ToNS;
49
50public:
51  NamespaceDiagnosticWatcher(StringRef From, StringRef To)
52      : Chained(nullptr), FromNS(From), ToNS("'"), SeenCount(0) {
53    ToNS.append(To);
54    ToNS.append("'");
55  }
56
57  virtual void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
58                                const Diagnostic &Info) {
59    if (Chained)
60      Chained->HandleDiagnostic(DiagLevel, Info);
61    if (Info.getID() - 1 == diag::err_using_directive_member_suggest) {
62      const IdentifierInfo *Ident = Info.getArgIdentifier(0);
63      const std::string &CorrectedQuotedStr = Info.getArgStdStr(1);
64      if (Ident->getName() == FromNS && CorrectedQuotedStr == ToNS)
65        ++SeenCount;
66    }
67  }
68
69  virtual void clear() {
70    DiagnosticConsumer::clear();
71    if (Chained)
72      Chained->clear();
73  }
74
75  virtual bool IncludeInDiagnosticCounts() const {
76    if (Chained)
77      return Chained->IncludeInDiagnosticCounts();
78    return false;
79  }
80
81  NamespaceDiagnosticWatcher *Chain(DiagnosticConsumer *ToChain) {
82    Chained = ToChain;
83    return this;
84  }
85
86  int SeenCount;
87};
88
89// \brief Always corrects a typo matching CorrectFrom with a new namespace
90// with the name CorrectTo.
91class NamespaceTypoProvider : public clang::ExternalSemaSource {
92  std::string CorrectFrom;
93  std::string CorrectTo;
94  Sema *CurrentSema;
95
96public:
97  NamespaceTypoProvider(StringRef From, StringRef To)
98      : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
99
100  virtual void InitializeSema(Sema &S) { CurrentSema = &S; }
101
102  virtual void ForgetSema() { CurrentSema = nullptr; }
103
104  virtual TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo,
105                                     int LookupKind, Scope *S, CXXScopeSpec *SS,
106                                     CorrectionCandidateCallback &CCC,
107                                     DeclContext *MemberContext,
108                                     bool EnteringContext,
109                                     const ObjCObjectPointerType *OPT) {
110    ++CallCount;
111    if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {
112      DeclContext *DestContext = nullptr;
113      ASTContext &Context = CurrentSema->getASTContext();
114      if (SS)
115        DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);
116      if (!DestContext)
117        DestContext = Context.getTranslationUnitDecl();
118      IdentifierInfo *ToIdent =
119          CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
120      NamespaceDecl *NewNamespace =
121          NamespaceDecl::Create(Context, DestContext, false, Typo.getBeginLoc(),
122                                Typo.getLoc(), ToIdent, nullptr);
123      DestContext->addDecl(NewNamespace);
124      TypoCorrection Correction(ToIdent);
125      Correction.addCorrectionDecl(NewNamespace);
126      return Correction;
127    }
128    return TypoCorrection();
129  }
130
131  int CallCount;
132};
133
134// \brief Chains together a vector of NamespaceDiagnosticWatchers and
135// adds a vector of ExternalSemaSources to the CompilerInstance before
136// performing semantic analysis.
137class ExternalSemaSourceInstaller : public clang::ASTFrontendAction {
138  std::vector<NamespaceDiagnosticWatcher *> Watchers;
139  std::vector<clang::ExternalSemaSource *> Sources;
140  std::unique_ptr<DiagnosticConsumer> OwnedClient;
141
142protected:
143  virtual clang::ASTConsumer *
144  CreateASTConsumer(clang::CompilerInstance &Compiler,
145                    llvm::StringRef /* dummy */) {
146    return new clang::ASTConsumer();
147  }
148
149  virtual void ExecuteAction() {
150    CompilerInstance &CI = getCompilerInstance();
151    ASSERT_FALSE(CI.hasSema());
152    CI.createSema(getTranslationUnitKind(), nullptr);
153    ASSERT_TRUE(CI.hasDiagnostics());
154    DiagnosticsEngine &Diagnostics = CI.getDiagnostics();
155    DiagnosticConsumer *Client = Diagnostics.getClient();
156    if (Diagnostics.ownsClient())
157      OwnedClient.reset(Diagnostics.takeClient());
158    for (size_t I = 0, E = Watchers.size(); I < E; ++I)
159      Client = Watchers[I]->Chain(Client);
160    Diagnostics.setClient(Client, false);
161    for (size_t I = 0, E = Sources.size(); I < E; ++I) {
162      Sources[I]->InitializeSema(CI.getSema());
163      CI.getSema().addExternalSource(Sources[I]);
164    }
165    ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats,
166             CI.getFrontendOpts().SkipFunctionBodies);
167  }
168
169public:
170  void PushSource(clang::ExternalSemaSource *Source) {
171    Sources.push_back(Source);
172  }
173
174  void PushWatcher(NamespaceDiagnosticWatcher *Watcher) {
175    Watchers.push_back(Watcher);
176  }
177};
178
179// Make sure that the NamespaceDiagnosticWatcher is not miscounting.
180TEST(ExternalSemaSource, SanityCheck) {
181  std::unique_ptr<ExternalSemaSourceInstaller> Installer(
182      new ExternalSemaSourceInstaller);
183  NamespaceDiagnosticWatcher Watcher("AAB", "BBB");
184  Installer->PushWatcher(&Watcher);
185  std::vector<std::string> Args(1, "-std=c++11");
186  ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
187      Installer.release(), "namespace AAA { } using namespace AAB;", Args));
188  ASSERT_EQ(0, Watcher.SeenCount);
189}
190
191// Check that when we add a NamespaceTypeProvider, we use that suggestion
192// instead of the usual suggestion we would use above.
193TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) {
194  std::unique_ptr<ExternalSemaSourceInstaller> Installer(
195      new ExternalSemaSourceInstaller);
196  NamespaceTypoProvider Provider("AAB", "BBB");
197  NamespaceDiagnosticWatcher Watcher("AAB", "BBB");
198  Installer->PushSource(&Provider);
199  Installer->PushWatcher(&Watcher);
200  std::vector<std::string> Args(1, "-std=c++11");
201  ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
202      Installer.release(), "namespace AAA { } using namespace AAB;", Args));
203  ASSERT_LE(0, Provider.CallCount);
204  ASSERT_EQ(1, Watcher.SeenCount);
205}
206
207// Check that we use the first successful TypoCorrection returned from an
208// ExternalSemaSource.
209TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) {
210  std::unique_ptr<ExternalSemaSourceInstaller> Installer(
211      new ExternalSemaSourceInstaller);
212  NamespaceTypoProvider First("XXX", "BBB");
213  NamespaceTypoProvider Second("AAB", "CCC");
214  NamespaceTypoProvider Third("AAB", "DDD");
215  NamespaceDiagnosticWatcher Watcher("AAB", "CCC");
216  Installer->PushSource(&First);
217  Installer->PushSource(&Second);
218  Installer->PushSource(&Third);
219  Installer->PushWatcher(&Watcher);
220  std::vector<std::string> Args(1, "-std=c++11");
221  ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
222      Installer.release(), "namespace AAA { } using namespace AAB;", Args));
223  ASSERT_LE(1, First.CallCount);
224  ASSERT_LE(1, Second.CallCount);
225  ASSERT_EQ(0, Third.CallCount);
226  ASSERT_EQ(1, Watcher.SeenCount);
227}
228
229// We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
230// solve the problem.
231TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {
232  std::unique_ptr<ExternalSemaSourceInstaller> Installer(
233      new ExternalSemaSourceInstaller);
234  CompleteTypeDiagnoser Diagnoser(false);
235  Installer->PushSource(&Diagnoser);
236  std::vector<std::string> Args(1, "-std=c++11");
237  // This code hits the class template specialization/class member of a class
238  // template specialization checks in Sema::RequireCompleteTypeImpl.
239  ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
240      Installer.release(),
241      "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
242      Args));
243  ASSERT_EQ(0, Diagnoser.CallCount);
244}
245
246// The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
247// true should be the last one called.
248TEST(ExternalSemaSource, FirstDiagnoserTaken) {
249  std::unique_ptr<ExternalSemaSourceInstaller> Installer(
250      new ExternalSemaSourceInstaller);
251  CompleteTypeDiagnoser First(false);
252  CompleteTypeDiagnoser Second(true);
253  CompleteTypeDiagnoser Third(true);
254  Installer->PushSource(&First);
255  Installer->PushSource(&Second);
256  Installer->PushSource(&Third);
257  std::vector<std::string> Args(1, "-std=c++11");
258  ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(
259      Installer.release(), "class Incomplete; Incomplete IncompleteInstance;",
260      Args));
261  ASSERT_EQ(1, First.CallCount);
262  ASSERT_EQ(1, Second.CallCount);
263  ASSERT_EQ(0, Third.CallCount);
264}
265
266} // anonymous namespace
267