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