Home | History | Annotate | Download | only in Sema
      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 
     22 using namespace clang;
     23 using namespace clang::tooling;
     24 
     25 namespace {
     26 
     27 // \brief Counts the number of times MaybeDiagnoseMissingCompleteType
     28 // is called. Returns the result it was provided on creation.
     29 class CompleteTypeDiagnoser : public clang::ExternalSemaSource {
     30 public:
     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.
     45 class NamespaceDiagnosticWatcher : public clang::DiagnosticConsumer {
     46   DiagnosticConsumer *Chained;
     47   std::string FromNS;
     48   std::string ToNS;
     49 
     50 public:
     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.
     91 class NamespaceTypoProvider : public clang::ExternalSemaSource {
     92   std::string CorrectFrom;
     93   std::string CorrectTo;
     94   Sema *CurrentSema;
     95 
     96 public:
     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.
    137 class ExternalSemaSourceInstaller : public clang::ASTFrontendAction {
    138   std::vector<NamespaceDiagnosticWatcher *> Watchers;
    139   std::vector<clang::ExternalSemaSource *> Sources;
    140   std::unique_ptr<DiagnosticConsumer> OwnedClient;
    141 
    142 protected:
    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 
    169 public:
    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.
    180 TEST(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.
    193 TEST(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.
    209 TEST(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.
    231 TEST(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.
    248 TEST(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