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