teak-llvm/clang/unittests/Sema/ExternalSemaSourceTest.cpp
Chandler Carruth 2946cd7010 Update the file headers across all of the LLVM projects in the monorepo
to reflect the new license.

We understand that people may be surprised that we're moving the header
entirely to discuss the new license. We checked this carefully with the
Foundation's lawyer and we believe this is the correct approach.

Essentially, all code in the project is now made available by the LLVM
project under our new license, so you will see that the license headers
include that license only. Some of our contributors have contributed
code under our old license, and accordingly, we have retained a copy of
our old license notice in the top-level files in each project and
repository.

llvm-svn: 351636
2019-01-19 08:50:56 +00:00

324 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(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 llvm::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) {
std::unique_ptr<ExternalSemaSourceInstaller> Installer(
new ExternalSemaSourceInstaller);
DiagnosticWatcher Watcher("AAB", "BBB");
Installer->PushWatcher(&Watcher);
std::vector<std::string> Args(1, "-std=c++11");
ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
Installer.release(), "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) {
std::unique_ptr<ExternalSemaSourceInstaller> Installer(
new 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(
Installer.release(), "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) {
std::unique_ptr<ExternalSemaSourceInstaller> Installer(
new 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(
Installer.release(), "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) {
std::unique_ptr<ExternalSemaSourceInstaller> Installer(
new 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(
Installer.release(), "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) {
std::unique_ptr<ExternalSemaSourceInstaller> Installer(
new 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(
Installer.release(),
"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) {
std::unique_ptr<ExternalSemaSourceInstaller> Installer(
new 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(
Installer.release(), "class Incomplete; Incomplete IncompleteInstance;",
Args));
ASSERT_EQ(1, First.CallCount);
ASSERT_EQ(1, Second.CallCount);
ASSERT_EQ(0, Third.CallCount);
}
} // anonymous namespace