1 //=== unittests/Sema/ExternalSemaSourceTest.cpp - ExternalSemaSource tests ===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "clang/AST/ASTConsumer.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/Frontend/CompilerInstance.h" 12 #include "clang/Lex/Preprocessor.h" 13 #include "clang/Parse/ParseAST.h" 14 #include "clang/Sema/ExternalSemaSource.h" 15 #include "clang/Sema/Sema.h" 16 #include "clang/Sema/SemaDiagnostic.h" 17 #include "clang/Sema/TypoCorrection.h" 18 #include "clang/Tooling/Tooling.h" 19 #include "gtest/gtest.h" 20 21 using namespace clang; 22 using namespace clang::tooling; 23 24 namespace { 25 26 // \brief Counts the number of times MaybeDiagnoseMissingCompleteType 27 // is called. Returns the result it was provided on creation. 28 class CompleteTypeDiagnoser : public clang::ExternalSemaSource { 29 public: 30 CompleteTypeDiagnoser(bool MockResult) : CallCount(0), Result(MockResult) {} 31 32 bool MaybeDiagnoseMissingCompleteType(SourceLocation L, QualType T) override { 33 ++CallCount; 34 return Result; 35 } 36 37 int CallCount; 38 bool Result; 39 }; 40 41 /// Counts the number of typo-correcting diagnostics correcting from one name to 42 /// another while still passing all diagnostics along a chain of consumers. 43 class DiagnosticWatcher : public clang::DiagnosticConsumer { 44 DiagnosticConsumer *Chained; 45 std::string FromName; 46 std::string ToName; 47 48 public: 49 DiagnosticWatcher(StringRef From, StringRef To) 50 : Chained(nullptr), FromName(From), ToName("'"), SeenCount(0) { 51 ToName.append(std::string(To)); 52 ToName.append("'"); 53 } 54 55 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, 56 const Diagnostic &Info) override { 57 if (Chained) 58 Chained->HandleDiagnostic(DiagLevel, Info); 59 if (Info.getID() - 1 == diag::err_using_directive_member_suggest) { 60 const IdentifierInfo *Ident = Info.getArgIdentifier(0); 61 const std::string &CorrectedQuotedStr = Info.getArgStdStr(1); 62 if (Ident->getName() == FromName && CorrectedQuotedStr == ToName) 63 ++SeenCount; 64 } else if (Info.getID() == diag::err_no_member_suggest) { 65 auto Ident = DeclarationName::getFromOpaqueInteger(Info.getRawArg(0)); 66 const std::string &CorrectedQuotedStr = Info.getArgStdStr(3); 67 if (Ident.getAsString() == FromName && CorrectedQuotedStr == ToName) 68 ++SeenCount; 69 } 70 } 71 72 void clear() override { 73 DiagnosticConsumer::clear(); 74 if (Chained) 75 Chained->clear(); 76 } 77 78 bool IncludeInDiagnosticCounts() const override { 79 if (Chained) 80 return Chained->IncludeInDiagnosticCounts(); 81 return false; 82 } 83 84 DiagnosticWatcher *Chain(DiagnosticConsumer *ToChain) { 85 Chained = ToChain; 86 return this; 87 } 88 89 int SeenCount; 90 }; 91 92 // \brief Always corrects a typo matching CorrectFrom with a new namespace 93 // with the name CorrectTo. 94 class NamespaceTypoProvider : public clang::ExternalSemaSource { 95 std::string CorrectFrom; 96 std::string CorrectTo; 97 Sema *CurrentSema; 98 99 public: 100 NamespaceTypoProvider(StringRef From, StringRef To) 101 : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {} 102 103 void InitializeSema(Sema &S) override { CurrentSema = &S; } 104 105 void ForgetSema() override { CurrentSema = nullptr; } 106 107 TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind, 108 Scope *S, CXXScopeSpec *SS, 109 CorrectionCandidateCallback &CCC, 110 DeclContext *MemberContext, bool EnteringContext, 111 const ObjCObjectPointerType *OPT) override { 112 ++CallCount; 113 if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) { 114 DeclContext *DestContext = nullptr; 115 ASTContext &Context = CurrentSema->getASTContext(); 116 if (SS) 117 DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext); 118 if (!DestContext) 119 DestContext = Context.getTranslationUnitDecl(); 120 IdentifierInfo *ToIdent = 121 CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo); 122 NamespaceDecl *NewNamespace = 123 NamespaceDecl::Create(Context, DestContext, false, Typo.getBeginLoc(), 124 Typo.getLoc(), ToIdent, nullptr); 125 DestContext->addDecl(NewNamespace); 126 TypoCorrection Correction(ToIdent); 127 Correction.addCorrectionDecl(NewNamespace); 128 return Correction; 129 } 130 return TypoCorrection(); 131 } 132 133 int CallCount; 134 }; 135 136 class FunctionTypoProvider : public clang::ExternalSemaSource { 137 std::string CorrectFrom; 138 std::string CorrectTo; 139 Sema *CurrentSema; 140 141 public: 142 FunctionTypoProvider(StringRef From, StringRef To) 143 : CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {} 144 145 void InitializeSema(Sema &S) override { CurrentSema = &S; } 146 147 void ForgetSema() override { CurrentSema = nullptr; } 148 149 TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind, 150 Scope *S, CXXScopeSpec *SS, 151 CorrectionCandidateCallback &CCC, 152 DeclContext *MemberContext, bool EnteringContext, 153 const ObjCObjectPointerType *OPT) override { 154 ++CallCount; 155 if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) { 156 DeclContext *DestContext = nullptr; 157 ASTContext &Context = CurrentSema->getASTContext(); 158 if (SS) 159 DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext); 160 if (!DestContext) 161 DestContext = Context.getTranslationUnitDecl(); 162 IdentifierInfo *ToIdent = 163 CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo); 164 auto *NewFunction = FunctionDecl::Create( 165 Context, DestContext, SourceLocation(), SourceLocation(), ToIdent, 166 Context.getFunctionType(Context.VoidTy, {}, {}), nullptr, SC_Static, 167 /*UsesFPIntrin*/ false); 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 std::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, DiagCheck) { 224 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 225 DiagnosticWatcher Watcher("AAB", "BBB"); 226 Installer->PushWatcher(&Watcher); 227 std::vector<std::string> Args(1, "-std=c++11"); 228 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 229 std::move(Installer), "namespace AAA { } using namespace AAB;", Args)); 230 ASSERT_EQ(0, Watcher.SeenCount); 231 } 232 233 // Check that when we add a NamespaceTypeProvider, we use that suggestion 234 // instead of the usual suggestion we would use above. 235 TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) { 236 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 237 NamespaceTypoProvider Provider("AAB", "BBB"); 238 DiagnosticWatcher Watcher("AAB", "BBB"); 239 Installer->PushSource(&Provider); 240 Installer->PushWatcher(&Watcher); 241 std::vector<std::string> Args(1, "-std=c++11"); 242 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 243 std::move(Installer), "namespace AAA { } using namespace AAB;", Args)); 244 ASSERT_LE(0, Provider.CallCount); 245 ASSERT_EQ(1, Watcher.SeenCount); 246 } 247 248 // Check that we use the first successful TypoCorrection returned from an 249 // ExternalSemaSource. 250 TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) { 251 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 252 NamespaceTypoProvider First("XXX", "BBB"); 253 NamespaceTypoProvider Second("AAB", "CCC"); 254 NamespaceTypoProvider Third("AAB", "DDD"); 255 DiagnosticWatcher Watcher("AAB", "CCC"); 256 Installer->PushSource(&First); 257 Installer->PushSource(&Second); 258 Installer->PushSource(&Third); 259 Installer->PushWatcher(&Watcher); 260 std::vector<std::string> Args(1, "-std=c++11"); 261 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 262 std::move(Installer), "namespace AAA { } using namespace AAB;", Args)); 263 ASSERT_LE(1, First.CallCount); 264 ASSERT_LE(1, Second.CallCount); 265 ASSERT_EQ(0, Third.CallCount); 266 ASSERT_EQ(1, Watcher.SeenCount); 267 } 268 269 TEST(ExternalSemaSource, ExternalDelayedTypoCorrection) { 270 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 271 FunctionTypoProvider Provider("aaa", "bbb"); 272 DiagnosticWatcher Watcher("aaa", "bbb"); 273 Installer->PushSource(&Provider); 274 Installer->PushWatcher(&Watcher); 275 std::vector<std::string> Args(1, "-std=c++11"); 276 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 277 std::move(Installer), "namespace AAA { } void foo() { AAA::aaa(); }", 278 Args)); 279 ASSERT_LE(0, Provider.CallCount); 280 ASSERT_EQ(1, Watcher.SeenCount); 281 } 282 283 // We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise 284 // solve the problem. 285 TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) { 286 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 287 CompleteTypeDiagnoser Diagnoser(false); 288 Installer->PushSource(&Diagnoser); 289 std::vector<std::string> Args(1, "-std=c++11"); 290 // This code hits the class template specialization/class member of a class 291 // template specialization checks in Sema::RequireCompleteTypeImpl. 292 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 293 std::move(Installer), 294 "template <typename T> struct S { class C { }; }; S<char>::C SCInst;", 295 Args)); 296 ASSERT_EQ(0, Diagnoser.CallCount); 297 } 298 299 // The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns 300 // true should be the last one called. 301 TEST(ExternalSemaSource, FirstDiagnoserTaken) { 302 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 303 CompleteTypeDiagnoser First(false); 304 CompleteTypeDiagnoser Second(true); 305 CompleteTypeDiagnoser Third(true); 306 Installer->PushSource(&First); 307 Installer->PushSource(&Second); 308 Installer->PushSource(&Third); 309 std::vector<std::string> Args(1, "-std=c++11"); 310 ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs( 311 std::move(Installer), "class Incomplete; Incomplete IncompleteInstance;", 312 Args)); 313 ASSERT_EQ(1, First.CallCount); 314 ASSERT_EQ(1, Second.CallCount); 315 ASSERT_EQ(0, Third.CallCount); 316 } 317 318 } // anonymous namespace 319