llvm-project/clang/unittests/Sema/ExternalSemaSourceTest.cpp

318 lines
12 KiB
C++

//=== unittests/Sema/ExternalSemaSourceTest.cpp - ExternalSemaSource tests ===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Parse/ParseAST.h"
#include "clang/Sema/ExternalSemaSource.h"
#include "clang/Sema/Sema.h"
#include "clang/Sema/SemaDiagnostic.h"
#include "clang/Sema/TypoCorrection.h"
#include "clang/Tooling/Tooling.h"
#include "gtest/gtest.h"
using namespace clang;
using namespace clang::tooling;
namespace {
// \brief Counts the number of times MaybeDiagnoseMissingCompleteType
// is called. Returns the result it was provided on creation.
class CompleteTypeDiagnoser : public clang::ExternalSemaSource {
public:
CompleteTypeDiagnoser(bool MockResult) : CallCount(0), Result(MockResult) {}
bool MaybeDiagnoseMissingCompleteType(SourceLocation L, QualType T) override {
++CallCount;
return Result;
}
int CallCount;
bool Result;
};
/// Counts the number of typo-correcting diagnostics correcting from one name to
/// another while still passing all diagnostics along a chain of consumers.
class DiagnosticWatcher : public clang::DiagnosticConsumer {
DiagnosticConsumer *Chained;
std::string FromName;
std::string ToName;
public:
DiagnosticWatcher(StringRef From, StringRef To)
: Chained(nullptr), FromName(From), ToName("'"), SeenCount(0) {
ToName.append(std::string(To));
ToName.append("'");
}
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) override {
if (Chained)
Chained->HandleDiagnostic(DiagLevel, Info);
if (Info.getID() - 1 == diag::err_using_directive_member_suggest) {
const IdentifierInfo *Ident = Info.getArgIdentifier(0);
const std::string &CorrectedQuotedStr = Info.getArgStdStr(1);
if (Ident->getName() == FromName && CorrectedQuotedStr == ToName)
++SeenCount;
} else if (Info.getID() == diag::err_no_member_suggest) {
auto Ident = DeclarationName::getFromOpaqueInteger(Info.getRawArg(0));
const std::string &CorrectedQuotedStr = Info.getArgStdStr(3);
if (Ident.getAsString() == FromName && CorrectedQuotedStr == ToName)
++SeenCount;
}
}
void clear() override {
DiagnosticConsumer::clear();
if (Chained)
Chained->clear();
}
bool IncludeInDiagnosticCounts() const override {
if (Chained)
return Chained->IncludeInDiagnosticCounts();
return false;
}
DiagnosticWatcher *Chain(DiagnosticConsumer *ToChain) {
Chained = ToChain;
return this;
}
int SeenCount;
};
// \brief Always corrects a typo matching CorrectFrom with a new namespace
// with the name CorrectTo.
class NamespaceTypoProvider : public clang::ExternalSemaSource {
std::string CorrectFrom;
std::string CorrectTo;
Sema *CurrentSema;
public:
NamespaceTypoProvider(StringRef From, StringRef To)
: CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
void InitializeSema(Sema &S) override { CurrentSema = &S; }
void ForgetSema() override { CurrentSema = nullptr; }
TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
Scope *S, CXXScopeSpec *SS,
CorrectionCandidateCallback &CCC,
DeclContext *MemberContext, bool EnteringContext,
const ObjCObjectPointerType *OPT) override {
++CallCount;
if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {
DeclContext *DestContext = nullptr;
ASTContext &Context = CurrentSema->getASTContext();
if (SS)
DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);
if (!DestContext)
DestContext = Context.getTranslationUnitDecl();
IdentifierInfo *ToIdent =
CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
NamespaceDecl *NewNamespace =
NamespaceDecl::Create(Context, DestContext, false, Typo.getBeginLoc(),
Typo.getLoc(), ToIdent, nullptr);
DestContext->addDecl(NewNamespace);
TypoCorrection Correction(ToIdent);
Correction.addCorrectionDecl(NewNamespace);
return Correction;
}
return TypoCorrection();
}
int CallCount;
};
class FunctionTypoProvider : public clang::ExternalSemaSource {
std::string CorrectFrom;
std::string CorrectTo;
Sema *CurrentSema;
public:
FunctionTypoProvider(StringRef From, StringRef To)
: CorrectFrom(From), CorrectTo(To), CurrentSema(nullptr), CallCount(0) {}
void InitializeSema(Sema &S) override { CurrentSema = &S; }
void ForgetSema() override { CurrentSema = nullptr; }
TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind,
Scope *S, CXXScopeSpec *SS,
CorrectionCandidateCallback &CCC,
DeclContext *MemberContext, bool EnteringContext,
const ObjCObjectPointerType *OPT) override {
++CallCount;
if (CurrentSema && Typo.getName().getAsString() == CorrectFrom) {
DeclContext *DestContext = nullptr;
ASTContext &Context = CurrentSema->getASTContext();
if (SS)
DestContext = CurrentSema->computeDeclContext(*SS, EnteringContext);
if (!DestContext)
DestContext = Context.getTranslationUnitDecl();
IdentifierInfo *ToIdent =
CurrentSema->getPreprocessor().getIdentifierInfo(CorrectTo);
auto *NewFunction = FunctionDecl::Create(
Context, DestContext, SourceLocation(), SourceLocation(), ToIdent,
Context.getFunctionType(Context.VoidTy, {}, {}), nullptr, SC_Static);
DestContext->addDecl(NewFunction);
TypoCorrection Correction(ToIdent);
Correction.addCorrectionDecl(NewFunction);
return Correction;
}
return TypoCorrection();
}
int CallCount;
};
// \brief Chains together a vector of DiagnosticWatchers and
// adds a vector of ExternalSemaSources to the CompilerInstance before
// performing semantic analysis.
class ExternalSemaSourceInstaller : public clang::ASTFrontendAction {
std::vector<DiagnosticWatcher *> Watchers;
std::vector<clang::ExternalSemaSource *> Sources;
std::unique_ptr<DiagnosticConsumer> OwnedClient;
protected:
std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance &Compiler,
llvm::StringRef /* dummy */) override {
return std::make_unique<clang::ASTConsumer>();
}
void ExecuteAction() override {
CompilerInstance &CI = getCompilerInstance();
ASSERT_FALSE(CI.hasSema());
CI.createSema(getTranslationUnitKind(), nullptr);
ASSERT_TRUE(CI.hasDiagnostics());
DiagnosticsEngine &Diagnostics = CI.getDiagnostics();
DiagnosticConsumer *Client = Diagnostics.getClient();
if (Diagnostics.ownsClient())
OwnedClient = Diagnostics.takeClient();
for (size_t I = 0, E = Watchers.size(); I < E; ++I)
Client = Watchers[I]->Chain(Client);
Diagnostics.setClient(Client, false);
for (size_t I = 0, E = Sources.size(); I < E; ++I) {
Sources[I]->InitializeSema(CI.getSema());
CI.getSema().addExternalSource(Sources[I]);
}
ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats,
CI.getFrontendOpts().SkipFunctionBodies);
}
public:
void PushSource(clang::ExternalSemaSource *Source) {
Sources.push_back(Source);
}
void PushWatcher(DiagnosticWatcher *Watcher) { Watchers.push_back(Watcher); }
};
// Make sure that the DiagnosticWatcher is not miscounting.
TEST(ExternalSemaSource, SanityCheck) {
auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
DiagnosticWatcher Watcher("AAB", "BBB");
Installer->PushWatcher(&Watcher);
std::vector<std::string> Args(1, "-std=c++11");
ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
std::move(Installer), "namespace AAA { } using namespace AAB;", Args));
ASSERT_EQ(0, Watcher.SeenCount);
}
// Check that when we add a NamespaceTypeProvider, we use that suggestion
// instead of the usual suggestion we would use above.
TEST(ExternalSemaSource, ExternalTypoCorrectionPrioritized) {
auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
NamespaceTypoProvider Provider("AAB", "BBB");
DiagnosticWatcher Watcher("AAB", "BBB");
Installer->PushSource(&Provider);
Installer->PushWatcher(&Watcher);
std::vector<std::string> Args(1, "-std=c++11");
ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
std::move(Installer), "namespace AAA { } using namespace AAB;", Args));
ASSERT_LE(0, Provider.CallCount);
ASSERT_EQ(1, Watcher.SeenCount);
}
// Check that we use the first successful TypoCorrection returned from an
// ExternalSemaSource.
TEST(ExternalSemaSource, ExternalTypoCorrectionOrdering) {
auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
NamespaceTypoProvider First("XXX", "BBB");
NamespaceTypoProvider Second("AAB", "CCC");
NamespaceTypoProvider Third("AAB", "DDD");
DiagnosticWatcher Watcher("AAB", "CCC");
Installer->PushSource(&First);
Installer->PushSource(&Second);
Installer->PushSource(&Third);
Installer->PushWatcher(&Watcher);
std::vector<std::string> Args(1, "-std=c++11");
ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
std::move(Installer), "namespace AAA { } using namespace AAB;", Args));
ASSERT_LE(1, First.CallCount);
ASSERT_LE(1, Second.CallCount);
ASSERT_EQ(0, Third.CallCount);
ASSERT_EQ(1, Watcher.SeenCount);
}
TEST(ExternalSemaSource, ExternalDelayedTypoCorrection) {
auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
FunctionTypoProvider Provider("aaa", "bbb");
DiagnosticWatcher Watcher("aaa", "bbb");
Installer->PushSource(&Provider);
Installer->PushWatcher(&Watcher);
std::vector<std::string> Args(1, "-std=c++11");
ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
std::move(Installer), "namespace AAA { } void foo() { AAA::aaa(); }",
Args));
ASSERT_LE(0, Provider.CallCount);
ASSERT_EQ(1, Watcher.SeenCount);
}
// We should only try MaybeDiagnoseMissingCompleteType if we can't otherwise
// solve the problem.
TEST(ExternalSemaSource, TryOtherTacticsBeforeDiagnosing) {
auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
CompleteTypeDiagnoser Diagnoser(false);
Installer->PushSource(&Diagnoser);
std::vector<std::string> Args(1, "-std=c++11");
// This code hits the class template specialization/class member of a class
// template specialization checks in Sema::RequireCompleteTypeImpl.
ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
std::move(Installer),
"template <typename T> struct S { class C { }; }; S<char>::C SCInst;",
Args));
ASSERT_EQ(0, Diagnoser.CallCount);
}
// The first ExternalSemaSource where MaybeDiagnoseMissingCompleteType returns
// true should be the last one called.
TEST(ExternalSemaSource, FirstDiagnoserTaken) {
auto Installer = std::make_unique<ExternalSemaSourceInstaller>();
CompleteTypeDiagnoser First(false);
CompleteTypeDiagnoser Second(true);
CompleteTypeDiagnoser Third(true);
Installer->PushSource(&First);
Installer->PushSource(&Second);
Installer->PushSource(&Third);
std::vector<std::string> Args(1, "-std=c++11");
ASSERT_FALSE(clang::tooling::runToolOnCodeWithArgs(
std::move(Installer), "class Incomplete; Incomplete IncompleteInstance;",
Args));
ASSERT_EQ(1, First.CallCount);
ASSERT_EQ(1, Second.CallCount);
ASSERT_EQ(0, Third.CallCount);
}
} // anonymous namespace