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   bool MaybeDiagnoseMissingCompleteType(SourceLocation L, QualType T) override {
     34     ++CallCount;
     35     return Result;
     36   }
     37 
     38   int CallCount;
     39   bool Result;
     40 };
     41 
     42 /// Counts the number of typo-correcting diagnostics correcting from one name to
     43 /// another while still passing all diagnostics along a chain of consumers.
     44 class DiagnosticWatcher : public clang::DiagnosticConsumer {
     45   DiagnosticConsumer *Chained;
     46   std::string FromName;
     47   std::string ToName;
     48 
     49 public:
     50   DiagnosticWatcher(StringRef From, StringRef To)
     51       : Chained(nullptr), FromName(From), ToName("'"), SeenCount(0) {
     52     ToName.append(To);
     53     ToName.append("'");
     54   }
     55 
     56   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
     57                         const Diagnostic &Info) override {
     58     if (Chained)
     59       Chained->HandleDiagnostic(DiagLevel, Info);
     60     if (Info.getID() - 1 == diag::err_using_directive_member_suggest) {
     61       const IdentifierInfo *Ident = Info.getArgIdentifier(0);
     62       const std::string &CorrectedQuotedStr = Info.getArgStdStr(1);
     63       if (Ident->getName() == FromName && CorrectedQuotedStr == ToName)
     64         ++SeenCount;
     65     } else if (Info.getID() == diag::err_no_member_suggest) {
     66       auto Ident = DeclarationName::getFromOpaqueInteger(Info.getRawArg(0));
     67       const std::string &CorrectedQuotedStr = Info.getArgStdStr(3);
     68       if (Ident.getAsString() == FromName && CorrectedQuotedStr == ToName)
     69         ++SeenCount;
     70     }
     71   }
     72 
     73   void clear() override {
     74     DiagnosticConsumer::clear();
     75     if (Chained)
     76       Chained->clear();
     77   }
     78 
     79   bool IncludeInDiagnosticCounts() const override {
     80     if (Chained)
     81       return Chained->IncludeInDiagnosticCounts();
     82     return false;
     83   }
     84 
     85   DiagnosticWatcher *Chain(DiagnosticConsumer *ToChain) {
     86     Chained = ToChain;
     87     return this;
     88   }
     89 
     90   int SeenCount;
     91 };
     92 
     93 // \brief Always corrects a typo matching CorrectFrom with a new namespace
     94 // with the name CorrectTo.
     95 class NamespaceTypoProvider : public clang::ExternalSemaSource {
     96   std::string CorrectFrom;
     97   std::string CorrectTo;
     98   Sema *CurrentSema;
     99 
    100 public:
    101   NamespaceTypoProvider(StringRef From, StringRef To)
    102       : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
    103 
    104   void InitializeSema(Sema &S) override { CurrentSema = &S; }
    105 
    106   void ForgetSema() override { CurrentSema = nullptr; }
    107 
    108   TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
    109                              Scope *S, CXXScopeSpec *SS,
    110                              CorrectionCandidateCallback &CCC,
    111                              DeclContext *MemberContext, bool EnteringContext,
    112                              const ObjCObjectPointerType *OPT) override {
    113     ++CallCount;
    114     if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {
    115       DeclContext *DestContext = nullptr;
    116       ASTContext &Context = CurrentSema->getASTContext();
    117       if (SS)
    118         DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);
    119       if (!DestContext)
    120         DestContext = Context.getTranslationUnitDecl();
    121       IdentifierInfo *ToIdent =
    122           CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
    123       NamespaceDecl *NewNamespace =
    124           NamespaceDecl::Create(Context, DestContext, false, Typo.getBeginLoc(),
    125                                 Typo.getLoc(), ToIdent, nullptr);
    126       DestContext->addDecl(NewNamespace);
    127       TypoCorrection Correction(ToIdent);
    128       Correction.addCorrectionDecl(NewNamespace);
    129       return Correction;
    130     }
    131     return TypoCorrection();
    132   }
    133 
    134   int CallCount;
    135 };
    136 
    137 class FunctionTypoProvider : public clang::ExternalSemaSource {
    138   std::string CorrectFrom;
    139   std::string CorrectTo;
    140   Sema *CurrentSema;
    141 
    142 public:
    143   FunctionTypoProvider(StringRef From, StringRef To)
    144       : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
    145 
    146   void InitializeSema(Sema &S) override { CurrentSema = &S; }
    147 
    148   void ForgetSema() override { CurrentSema = nullptr; }
    149 
    150   TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
    151                              Scope *S, CXXScopeSpec *SS,
    152                              CorrectionCandidateCallback &CCC,
    153                              DeclContext *MemberContext, bool EnteringContext,
    154                              const ObjCObjectPointerType *OPT) override {
    155     ++CallCount;
    156     if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {
    157       DeclContext *DestContext = nullptr;
    158       ASTContext &Context = CurrentSema->getASTContext();
    159       if (SS)
    160         DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);
    161       if (!DestContext)
    162         DestContext = Context.getTranslationUnitDecl();
    163       IdentifierInfo *ToIdent =
    164           CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
    165       auto *NewFunction = FunctionDecl::Create(
    166           Context, DestContext, SourceLocation(), SourceLocation(), ToIdent,
    167           Context.getFunctionType(Context.VoidTy, {}, {}), nullptr, SC_Static);
    168       DestContext->addDecl(NewFunction);
    169       TypoCorrection Correction(ToIdent);
    170       Correction.addCorrectionDecl(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 llvm::make_unique<clang::ASTConsumer>();
    192   }
    193 
    194   void ExecuteAction() override {
    195     CompilerInstance &CI = getCompilerInstance();
    196     ASSERT_FALSE(CI.hasSema());
    197     CI.createSema(getTranslationUnitKind(), 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(Client);
    205     Diagnostics.setClient(Client, false);
    206     for (size_t I = 0, E = Sources.size(); I < E; ++I) {
    207       Sources[I]->InitializeSema(CI.getSema());
    208       CI.getSema().addExternalSource(Sources[I]);
    209     }
    210     ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats,
    211              CI.getFrontendOpts().SkipFunctionBodies);
    212   }
    213 
    214 public:
    215   void PushSource(clang::ExternalSemaSource *Source) {
    216     Sources.push_back(Source);
    217   }
    218 
    219   void PushWatcher(DiagnosticWatcher *Watcher) { Watchers.push_back(Watcher); }
    220 };
    221 
    222 // Make sure that the DiagnosticWatcher is not miscounting.
    223 TEST(ExternalSemaSource, SanityCheck) {
    224   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
    225       new ExternalSemaSourceInstaller);
    226   DiagnosticWatcher Watcher("AAB", "BBB");
    227   Installer->PushWatcher(&Watcher);
    228   std::vector<std::string> Args(1, "-std=c++11");
    229   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
    230       Installer.release(), "namespace AAA { } using namespace AAB;", Args));
    231   ASSERT_EQ(0, Watcher.SeenCount);
    232 }
    233 
    234 // Check that when we add a NamespaceTypeProvider, we use that suggestion
    235 // instead of the usual suggestion we would use above.
    236 TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) {
    237   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
    238       new ExternalSemaSourceInstaller);
    239   NamespaceTypoProvider Provider("AAB", "BBB");
    240   DiagnosticWatcher Watcher("AAB", "BBB");
    241   Installer->PushSource(&Provider);
    242   Installer->PushWatcher(&Watcher);
    243   std::vector<std::string> Args(1, "-std=c++11");
    244   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
    245       Installer.release(), "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   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
    254       new ExternalSemaSourceInstaller);
    255   NamespaceTypoProvider First("XXX", "BBB");
    256   NamespaceTypoProvider Second("AAB", "CCC");
    257   NamespaceTypoProvider Third("AAB", "DDD");
    258   DiagnosticWatcher Watcher("AAB", "CCC");
    259   Installer->PushSource(&First);
    260   Installer->PushSource(&Second);
    261   Installer->PushSource(&Third);
    262   Installer->PushWatcher(&Watcher);
    263   std::vector<std::string> Args(1, "-std=c++11");
    264   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
    265       Installer.release(), "namespace AAA { } using namespace AAB;", Args));
    266   ASSERT_LE(1, First.CallCount);
    267   ASSERT_LE(1, Second.CallCount);
    268   ASSERT_EQ(0, Third.CallCount);
    269   ASSERT_EQ(1, Watcher.SeenCount);
    270 }
    271 
    272 TEST(ExternalSemaSource, ExternalDelayedTypoCorrection) {
    273   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
    274       new ExternalSemaSourceInstaller);
    275   FunctionTypoProvider Provider("aaa", "bbb");
    276   DiagnosticWatcher Watcher("aaa", "bbb");
    277   Installer->PushSource(&Provider);
    278   Installer->PushWatcher(&Watcher);
    279   std::vector<std::string> Args(1, "-std=c++11");
    280   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
    281       Installer.release(), "namespace AAA { } void foo() { AAA::aaa(); }",
    282       Args));
    283   ASSERT_LE(0, Provider.CallCount);
    284   ASSERT_EQ(1, Watcher.SeenCount);
    285 }
    286 
    287 // We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
    288 // solve the problem.
    289 TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {
    290   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
    291       new ExternalSemaSourceInstaller);
    292   CompleteTypeDiagnoser Diagnoser(false);
    293   Installer->PushSource(&Diagnoser);
    294   std::vector<std::string> Args(1, "-std=c++11");
    295   // This code hits the class template specialization/class member of a class
    296   // template specialization checks in Sema::RequireCompleteTypeImpl.
    297   ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
    298       Installer.release(),
    299       "template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
    300       Args));
    301   ASSERT_EQ(0, Diagnoser.CallCount);
    302 }
    303 
    304 // The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
    305 // true should be the last one called.
    306 TEST(ExternalSemaSource, FirstDiagnoserTaken) {
    307   std::unique_ptr<ExternalSemaSourceInstaller> Installer(
    308       new ExternalSemaSourceInstaller);
    309   CompleteTypeDiagnoser First(false);
    310   CompleteTypeDiagnoser Second(true);
    311   CompleteTypeDiagnoser Third(true);
    312   Installer->PushSource(&First);
    313   Installer->PushSource(&Second);
    314   Installer->PushSource(&Third);
    315   std::vector<std::string> Args(1, "-std=c++11");
    316   ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(
    317       Installer.release(), "class Incomplete; Incomplete IncompleteInstance;",
    318       Args));
    319   ASSERT_EQ(1, First.CallCount);
    320   ASSERT_EQ(1, Second.CallCount);
    321   ASSERT_EQ(0, Third.CallCount);
    322 }
    323 
    324 } // anonymous namespace
    325