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 // \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 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, 58 const Diagnostic &Info) override { 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 void clear() override { 70 DiagnosticConsumer::clear(); 71 if (Chained) 72 Chained->clear(); 73 } 74 75 bool IncludeInDiagnosticCounts() const override { 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 void InitializeSema(Sema &S) override { CurrentSema = &S; } 101 102 void ForgetSema() override { CurrentSema = nullptr; } 103 104 TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind, 105 Scope *S, CXXScopeSpec *SS, 106 CorrectionCandidateCallback &CCC, 107 DeclContext *MemberContext, bool EnteringContext, 108 const ObjCObjectPointerType *OPT) override { 109 ++CallCount; 110 if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) { 111 DeclContext *DestContext = nullptr; 112 ASTContext &Context = CurrentSema->getASTContext(); 113 if (SS) 114 DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext); 115 if (!DestContext) 116 DestContext = Context.getTranslationUnitDecl(); 117 IdentifierInfo *ToIdent = 118 CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo); 119 NamespaceDecl *NewNamespace = 120 NamespaceDecl::Create(Context, DestContext, false, Typo.getBeginLoc(), 121 Typo.getLoc(), ToIdent, nullptr); 122 DestContext->addDecl(NewNamespace); 123 TypoCorrection Correction(ToIdent); 124 Correction.addCorrectionDecl(NewNamespace); 125 return Correction; 126 } 127 return TypoCorrection(); 128 } 129 130 int CallCount; 131 }; 132 133 // \brief Chains together a vector of NamespaceDiagnosticWatchers and 134 // adds a vector of ExternalSemaSources to the CompilerInstance before 135 // performing semantic analysis. 136 class ExternalSemaSourceInstaller : public clang::ASTFrontendAction { 137 std::vector<NamespaceDiagnosticWatcher *> Watchers; 138 std::vector<clang::ExternalSemaSource *> Sources; 139 std::unique_ptr<DiagnosticConsumer> OwnedClient; 140 141 protected: 142 std::unique_ptr<clang::ASTConsumer> 143 CreateASTConsumer(clang::CompilerInstance &Compiler, 144 llvm::StringRef /* dummy */) override { 145 return llvm::make_unique<clang::ASTConsumer>(); 146 } 147 148 void ExecuteAction() override { 149 CompilerInstance &CI = getCompilerInstance(); 150 ASSERT_FALSE(CI.hasSema()); 151 CI.createSema(getTranslationUnitKind(), nullptr); 152 ASSERT_TRUE(CI.hasDiagnostics()); 153 DiagnosticsEngine &Diagnostics = CI.getDiagnostics(); 154 DiagnosticConsumer *Client = Diagnostics.getClient(); 155 if (Diagnostics.ownsClient()) 156 OwnedClient = Diagnostics.takeClient(); 157 for (size_t I = 0, E = Watchers.size(); I < E; ++I) 158 Client = Watchers[I]->Chain(Client); 159 Diagnostics.setClient(Client, false); 160 for (size_t I = 0, E = Sources.size(); I < E; ++I) { 161 Sources[I]->InitializeSema(CI.getSema()); 162 CI.getSema().addExternalSource(Sources[I]); 163 } 164 ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats, 165 CI.getFrontendOpts().SkipFunctionBodies); 166 } 167 168 public: 169 void PushSource(clang::ExternalSemaSource *Source) { 170 Sources.push_back(Source); 171 } 172 173 void PushWatcher(NamespaceDiagnosticWatcher *Watcher) { 174 Watchers.push_back(Watcher); 175 } 176 }; 177 178 // Make sure that the NamespaceDiagnosticWatcher is not miscounting. 179 TEST(ExternalSemaSource, SanityCheck) { 180 std::unique_ptr<ExternalSemaSourceInstaller> Installer( 181 new ExternalSemaSourceInstaller); 182 NamespaceDiagnosticWatcher Watcher("AAB", "BBB"); 183 Installer->PushWatcher(&Watcher); 184 std::vector<std::string> Args(1, "-std=c++11"); 185 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 186 Installer.release(), "namespace AAA { } using namespace AAB;", Args)); 187 ASSERT_EQ(0, Watcher.SeenCount); 188 } 189 190 // Check that when we add a NamespaceTypeProvider, we use that suggestion 191 // instead of the usual suggestion we would use above. 192 TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) { 193 std::unique_ptr<ExternalSemaSourceInstaller> Installer( 194 new ExternalSemaSourceInstaller); 195 NamespaceTypoProvider Provider("AAB", "BBB"); 196 NamespaceDiagnosticWatcher Watcher("AAB", "BBB"); 197 Installer->PushSource(&Provider); 198 Installer->PushWatcher(&Watcher); 199 std::vector<std::string> Args(1, "-std=c++11"); 200 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 201 Installer.release(), "namespace AAA { } using namespace AAB;", Args)); 202 ASSERT_LE(0, Provider.CallCount); 203 ASSERT_EQ(1, Watcher.SeenCount); 204 } 205 206 // Check that we use the first successful TypoCorrection returned from an 207 // ExternalSemaSource. 208 TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) { 209 std::unique_ptr<ExternalSemaSourceInstaller> Installer( 210 new ExternalSemaSourceInstaller); 211 NamespaceTypoProvider First("XXX", "BBB"); 212 NamespaceTypoProvider Second("AAB", "CCC"); 213 NamespaceTypoProvider Third("AAB", "DDD"); 214 NamespaceDiagnosticWatcher Watcher("AAB", "CCC"); 215 Installer->PushSource(&First); 216 Installer->PushSource(&Second); 217 Installer->PushSource(&Third); 218 Installer->PushWatcher(&Watcher); 219 std::vector<std::string> Args(1, "-std=c++11"); 220 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 221 Installer.release(), "namespace AAA { } using namespace AAB;", Args)); 222 ASSERT_LE(1, First.CallCount); 223 ASSERT_LE(1, Second.CallCount); 224 ASSERT_EQ(0, Third.CallCount); 225 ASSERT_EQ(1, Watcher.SeenCount); 226 } 227 228 // We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise 229 // solve the problem. 230 TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) { 231 std::unique_ptr<ExternalSemaSourceInstaller> Installer( 232 new ExternalSemaSourceInstaller); 233 CompleteTypeDiagnoser Diagnoser(false); 234 Installer->PushSource(&Diagnoser); 235 std::vector<std::string> Args(1, "-std=c++11"); 236 // This code hits the class template specialization/class member of a class 237 // template specialization checks in Sema::RequireCompleteTypeImpl. 238 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 239 Installer.release(), 240 "template <typename T> struct S { class C { }; }; S<char>::C SCInst;", 241 Args)); 242 ASSERT_EQ(0, Diagnoser.CallCount); 243 } 244 245 // The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns 246 // true should be the last one called. 247 TEST(ExternalSemaSource, FirstDiagnoserTaken) { 248 std::unique_ptr<ExternalSemaSourceInstaller> Installer( 249 new ExternalSemaSourceInstaller); 250 CompleteTypeDiagnoser First(false); 251 CompleteTypeDiagnoser Second(true); 252 CompleteTypeDiagnoser Third(true); 253 Installer->PushSource(&First); 254 Installer->PushSource(&Second); 255 Installer->PushSource(&Third); 256 std::vector<std::string> Args(1, "-std=c++11"); 257 ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs( 258 Installer.release(), "class Incomplete; Incomplete IncompleteInstance;", 259 Args)); 260 ASSERT_EQ(1, First.CallCount); 261 ASSERT_EQ(1, Second.CallCount); 262 ASSERT_EQ(0, Third.CallCount); 263 } 264 265 } // anonymous namespace 266