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 DestContext->addDecl(NewFunction); 168 TypoCorrection Correction(ToIdent); 169 Correction.addCorrectionDecl(NewFunction); 170 return Correction; 171 } 172 return TypoCorrection(); 173 } 174 175 int CallCount; 176 }; 177 178 // \brief Chains together a vector of DiagnosticWatchers and 179 // adds a vector of ExternalSemaSources to the CompilerInstance before 180 // performing semantic analysis. 181 class ExternalSemaSourceInstaller : public clang::ASTFrontendAction { 182 std::vector<DiagnosticWatcher *> Watchers; 183 std::vector<clang::ExternalSemaSource *> Sources; 184 std::unique_ptr<DiagnosticConsumer> OwnedClient; 185 186 protected: 187 std::unique_ptr<clang::ASTConsumer> 188 CreateASTConsumer(clang::CompilerInstance &Compiler, 189 llvm::StringRef /* dummy */) override { 190 return std::make_unique<clang::ASTConsumer>(); 191 } 192 193 void ExecuteAction() override { 194 CompilerInstance &CI = getCompilerInstance(); 195 ASSERT_FALSE(CI.hasSema()); 196 CI.createSema(getTranslationUnitKind(), nullptr); 197 ASSERT_TRUE(CI.hasDiagnostics()); 198 DiagnosticsEngine &Diagnostics = CI.getDiagnostics(); 199 DiagnosticConsumer *Client = Diagnostics.getClient(); 200 if (Diagnostics.ownsClient()) 201 OwnedClient = Diagnostics.takeClient(); 202 for (size_t I = 0, E = Watchers.size(); I < E; ++I) 203 Client = Watchers[I]->Chain(Client); 204 Diagnostics.setClient(Client, false); 205 for (size_t I = 0, E = Sources.size(); I < E; ++I) { 206 Sources[I]->InitializeSema(CI.getSema()); 207 CI.getSema().addExternalSource(Sources[I]); 208 } 209 ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats, 210 CI.getFrontendOpts().SkipFunctionBodies); 211 } 212 213 public: 214 void PushSource(clang::ExternalSemaSource *Source) { 215 Sources.push_back(Source); 216 } 217 218 void PushWatcher(DiagnosticWatcher *Watcher) { Watchers.push_back(Watcher); } 219 }; 220 221 // Make sure that the DiagnosticWatcher is not miscounting. 222 TEST(ExternalSemaSource, SanityCheck) { 223 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 224 DiagnosticWatcher Watcher("AAB", "BBB"); 225 Installer->PushWatcher(&Watcher); 226 std::vector<std::string> Args(1, "-std=c++11"); 227 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 228 std::move(Installer), "namespace AAA { } using namespace AAB;", Args)); 229 ASSERT_EQ(0, Watcher.SeenCount); 230 } 231 232 // Check that when we add a NamespaceTypeProvider, we use that suggestion 233 // instead of the usual suggestion we would use above. 234 TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) { 235 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 236 NamespaceTypoProvider Provider("AAB", "BBB"); 237 DiagnosticWatcher Watcher("AAB", "BBB"); 238 Installer->PushSource(&Provider); 239 Installer->PushWatcher(&Watcher); 240 std::vector<std::string> Args(1, "-std=c++11"); 241 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 242 std::move(Installer), "namespace AAA { } using namespace AAB;", Args)); 243 ASSERT_LE(0, Provider.CallCount); 244 ASSERT_EQ(1, Watcher.SeenCount); 245 } 246 247 // Check that we use the first successful TypoCorrection returned from an 248 // ExternalSemaSource. 249 TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) { 250 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 251 NamespaceTypoProvider First("XXX", "BBB"); 252 NamespaceTypoProvider Second("AAB", "CCC"); 253 NamespaceTypoProvider Third("AAB", "DDD"); 254 DiagnosticWatcher Watcher("AAB", "CCC"); 255 Installer->PushSource(&First); 256 Installer->PushSource(&Second); 257 Installer->PushSource(&Third); 258 Installer->PushWatcher(&Watcher); 259 std::vector<std::string> Args(1, "-std=c++11"); 260 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 261 std::move(Installer), "namespace AAA { } using namespace AAB;", Args)); 262 ASSERT_LE(1, First.CallCount); 263 ASSERT_LE(1, Second.CallCount); 264 ASSERT_EQ(0, Third.CallCount); 265 ASSERT_EQ(1, Watcher.SeenCount); 266 } 267 268 TEST(ExternalSemaSource, ExternalDelayedTypoCorrection) { 269 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 270 FunctionTypoProvider Provider("aaa", "bbb"); 271 DiagnosticWatcher Watcher("aaa", "bbb"); 272 Installer->PushSource(&Provider); 273 Installer->PushWatcher(&Watcher); 274 std::vector<std::string> Args(1, "-std=c++11"); 275 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 276 std::move(Installer), "namespace AAA { } void foo() { AAA::aaa(); }", 277 Args)); 278 ASSERT_LE(0, Provider.CallCount); 279 ASSERT_EQ(1, Watcher.SeenCount); 280 } 281 282 // We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise 283 // solve the problem. 284 TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) { 285 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 286 CompleteTypeDiagnoser Diagnoser(false); 287 Installer->PushSource(&Diagnoser); 288 std::vector<std::string> Args(1, "-std=c++11"); 289 // This code hits the class template specialization/class member of a class 290 // template specialization checks in Sema::RequireCompleteTypeImpl. 291 ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs( 292 std::move(Installer), 293 "template <typename T> struct S { class C { }; }; S<char>::C SCInst;", 294 Args)); 295 ASSERT_EQ(0, Diagnoser.CallCount); 296 } 297 298 // The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns 299 // true should be the last one called. 300 TEST(ExternalSemaSource, FirstDiagnoserTaken) { 301 auto Installer = std::make_unique<ExternalSemaSourceInstaller>(); 302 CompleteTypeDiagnoser First(false); 303 CompleteTypeDiagnoser Second(true); 304 CompleteTypeDiagnoser Third(true); 305 Installer->PushSource(&First); 306 Installer->PushSource(&Second); 307 Installer->PushSource(&Third); 308 std::vector<std::string> Args(1, "-std=c++11"); 309 ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs( 310 std::move(Installer), "class Incomplete; Incomplete IncompleteInstance;", 311 Args)); 312 ASSERT_EQ(1, First.CallCount); 313 ASSERT_EQ(1, Second.CallCount); 314 ASSERT_EQ(0, Third.CallCount); 315 } 316 317 } // anonymous namespace 318