blob: 6697d566de97ded96b7138cde515013ac654be8e [file] [log] [blame]
//===--- DependencyVerifier.cpp - Dependency Verifier ---------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Implements a verifier for dependencies registered against the
// ReferencedNameTracker in a SourceFile.
//
//===----------------------------------------------------------------------===//
#include "swift/AST/ASTContext.h"
#include "swift/AST/ASTMangler.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/AST/SourceFile.h"
#include "swift/Basic/OptionSet.h"
#include "swift/Demangling/Demangler.h"
#include "swift/Frontend/DiagnosticVerifier.h"
#include "swift/Parse/Lexer.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/Support/FormatVariadic.h"
using namespace swift;
namespace {
/// An \c Expectation represents a user-provided expectation for a particular
/// dependency entry. An expectation is usually written in-line in a comment
/// attached near the relevant declaration and takes one of the following forms:
///
/// // expected-provides {{ProvidedName}}
/// // expected-member {{some.User.member}}
///
/// An expectation contains additional information about the expectation
/// \c Kind, which matches one of the few kinds of dependency entry that are
/// currently representable in the dependency graph.
///
/// As not all combinations of scopes and kinds makes sense, and to ease the
/// addition of further combinations of the two, the supported set of
/// expectations is given by the following matrix:
///
#define EXPECTATION_MATRIX \
MATRIX_ENTRY("expected-no-dependency", Negative) \
MATRIX_ENTRY("expected-provides", Provides) \
MATRIX_ENTRY("expected-superclass", Superclass) \
MATRIX_ENTRY("expected-conformance", Conformance) \
MATRIX_ENTRY("expected-member", Member) \
MATRIX_ENTRY("expected-dynamic-member", DynamicMember) \
///
/// To add a new supported combination, update \c Expectation::Kind
/// then define a new \c MATRIX_ENTRY with the following information:
///
/// MATRIX_ENTRY(<Expectation-Selector-String>, Expectation::Kind)
///
/// Where \c <Expectation-Selector-String> matches the grammar for an
/// expectation. The verifier's parsing routines use this matrix to
/// automatically keep the parser in harmony with the internal representation of
/// the expectation.
struct Expectation {
public:
enum class Kind : uint8_t {
Negative,
Provides,
Member,
PotentialMember,
Superclass = PotentialMember,
Conformance = PotentialMember,
DynamicMember,
};
/// The full range of the "expected-foo {{}}".
const char *ExpectedStart, *ExpectedEnd = nullptr;
/// Additional information about the expectation.
struct {
Expectation::Kind Kind;
} Info;
/// The raw input buffer for the message text, the part in the {{...}}
StringRef MessageRange;
public:
Expectation(const char *estart, const char *eend, Expectation::Kind k,
StringRef r)
: ExpectedStart(estart), ExpectedEnd(eend), Info{k}, MessageRange(r) {
assert(ExpectedStart <= MessageRange.data() &&
"Message range appears before expected start!");
assert(MessageRange.data() + MessageRange.size() <= ExpectedEnd &&
"Message range extends beyond expected end!");
}
};
/// An \c Obligation represents a compiler-provided entry in the set of
/// dependencies for a given source file. Similar to an \c Expectation an
/// \c Obligation contains a name, information about its kind and flavor, and an
/// extra source location that can be used to guide where diagnostics are
/// emitted. Unlike an \c Expectation, it provides an extra piece of state that
/// represents the obligation's "fulfillment status".
///
/// All \c Obligations begin in the \c Owed state. Once an obligation has been
/// paired with a matching \c Expectation, the obligation may then transition to
/// either \c Fulfilled if it has been satisfied completely, or \c Failed
/// otherwise. The verifier turns all unfulfilled obligations into errors.
struct Obligation {
/// The state of an \c Obligation
enum class State : uint8_t {
/// The \c Obligation is owed and has not been paired with a corresponding
/// \c Expectation.
Owed,
/// The \c Obligation is fulfilled.
Fulfilled,
/// The \c Obligation was matched against an \c Expectation, but that
/// expectation could not
/// fulfill the obligation because additional requirements did not pass.
Failed,
};
/// A token returned when an \c Obligation is fulfilled or failed. An \c
/// Obligation is the only type that may construct fulfillment tokens.
///
/// \c FullfillmentToken prevents misuse of the \c Obligation
/// structure by requiring its state to be changed along all program paths.
struct FullfillmentToken {
friend Obligation;
private:
FullfillmentToken() = default;
};
/// An \c Obligation::Key is a reduced set of the common data contained in an
/// \c Obligation and an \c Expectation.
///
/// This provides a way to use a value of either type to index into an \c
/// ObligationMap.
struct Key {
StringRef Name;
Expectation::Kind Kind;
public:
Key() = delete;
public:
static Key forNegative(StringRef name) {
return Key{name, Expectation::Kind::Negative};
}
static Key forProvides(StringRef name) {
return Key{name, Expectation::Kind::Provides};
}
static Key forDynamicMember(StringRef name) {
return Key{name, Expectation::Kind::DynamicMember};
}
static Key forPotentialMember(StringRef name) {
return Key{name, Expectation::Kind::PotentialMember};
}
static Key forMember(StringRef name) {
return Key{name, Expectation::Kind::Member};
}
static Key forExpectation(const Expectation &E) {
return Key{E.MessageRange, E.Info.Kind};
}
public:
struct Info {
static inline Obligation::Key getEmptyKey() {
return Obligation::Key{llvm::DenseMapInfo<StringRef>::getEmptyKey(),
static_cast<Expectation::Kind>(~0)};
}
static inline Obligation::Key getTombstoneKey() {
return Obligation::Key{llvm::DenseMapInfo<StringRef>::getTombstoneKey(),
static_cast<Expectation::Kind>(~0U - 1)};
}
static unsigned getHashValue(const Obligation::Key &Val) {
return llvm::hash_combine(Val.Name, Val.Kind);
}
static bool isEqual(const Obligation::Key &LHS,
const Obligation::Key &RHS) {
return LHS.Name == RHS.Name && LHS.Kind == RHS.Kind;
}
};
};
private:
StringRef name;
Expectation::Kind kind;
State state;
public:
Obligation(StringRef name, Expectation::Kind k)
: name(name), kind{k}, state(State::Owed) {
assert(k != Expectation::Kind::Negative &&
"Cannot form negative obligation!");
}
Expectation::Kind getKind() const { return kind; }
StringRef getName() const { return name; }
StringRef renderAsFixit(ASTContext &Ctx) const {
llvm::StringRef selector =
#define MATRIX_ENTRY(SELECTOR, KIND) \
if (getKind() == Expectation::Kind::KIND) { \
return SELECTOR; \
}
[this]() -> StringRef {
EXPECTATION_MATRIX
return "";
}();
#undef MATRIX_ENTRY
return Ctx.AllocateCopy(("// " + selector + "{{" + getName() + "}}").str());
}
public:
bool isOwed() const { return state == State::Owed; }
FullfillmentToken fullfill() {
assert(state == State::Owed &&
"Cannot fulfill an obligation more than once!");
state = State::Fulfilled;
return FullfillmentToken{};
}
FullfillmentToken fail() {
assert(state == State::Owed && "Cannot fail an obligation more than once!");
state = State::Failed;
return FullfillmentToken{};
}
};
/// The \c DependencyVerifier implements routines to verify a set of \c
/// Expectations in a given source file meet and match a set of \c Obligations
/// in the referenced name trackers associated with that file.
class DependencyVerifier {
SourceManager &SM;
public:
explicit DependencyVerifier(SourceManager &SM) : SM(SM) {}
bool verifyFile(const SourceFile *SF);
public:
using ObligationMap = llvm::MapVector<
Obligation::Key, Obligation,
llvm::DenseMap<Obligation::Key, unsigned, Obligation::Key::Info>>;
using NegativeExpectationMap = llvm::StringMap<Expectation>;
private:
/// These routines return \c true on failure, \c false otherwise.
bool parseExpectations(const SourceFile *SF,
std::vector<Expectation> &Expectations);
bool constructObligations(const SourceFile *SF, ObligationMap &map);
bool verifyObligations(const SourceFile *SF,
const std::vector<Expectation> &Exs,
ObligationMap &Obs,
NegativeExpectationMap &NegativeExpectations);
bool verifyNegativeExpectations(const SourceFile *SF, ObligationMap &Obs,
NegativeExpectationMap &Negs);
bool diagnoseUnfulfilledObligations(const SourceFile *SF, ObligationMap &OM);
private:
/// Given an \c ObligationMap and an \c Expectation, attempt to identify a
/// corresponding owed \c Obligation and verify it. If there is a matching
/// obligation, the \p fulfill callback is given the obligation. Otherwise \p
/// fail is called with the unmatched expectation value.
void matchExpectationOrFail(
ObligationMap &OM, const Expectation &expectation,
llvm::function_ref<Obligation::FullfillmentToken(Obligation &)> fulfill,
llvm::function_ref<void(const Expectation &)> fail) {
auto entry = OM.find(Obligation::Key::forExpectation(expectation));
if (entry == OM.end()) {
return fail(expectation);
} else {
fulfill(entry->second);
}
}
/// For each owed \c Obligation, call the provided callback with its
/// relevant name data and the Obligation itself.
void
forEachOwedObligation(ObligationMap &OM,
llvm::function_ref<void(StringRef, Obligation &)> f) {
for (auto &p : OM) {
if (p.second.isOwed())
f(p.first.Name, p.second);
}
}
private:
StringRef copyQualifiedTypeName(ASTContext &Ctx, NominalTypeDecl *subject) {
auto printOptions = PrintOptions();
printOptions.FullyQualifiedTypes = true;
auto key = subject->getDeclaredInterfaceType()->getString(printOptions);
return Ctx.AllocateCopy(key);
}
private:
template <typename... ArgTypes>
InFlightDiagnostic
diagnose(DiagnosticEngine &Diags, const char *LocPtr, Diag<ArgTypes...> ID,
typename detail::PassArgument<ArgTypes>::type... Args) const {
auto Loc = SourceLoc(llvm::SMLoc::getFromPointer(LocPtr));
return Diags.diagnose(Loc, ID, std::move(Args)...);
}
};
} // end anonymous namespace
bool DependencyVerifier::parseExpectations(
const SourceFile *SF, std::vector<Expectation> &Expectations) {
const auto MaybeBufferID = SF->getBufferID();
if (!MaybeBufferID) {
llvm::errs() << "source file has no buffer: " << SF->getFilename();
return true;
}
const auto BufferID = MaybeBufferID.getValue();
const CharSourceRange EntireRange = SM.getRangeForBuffer(BufferID);
const StringRef InputFile = SM.extractText(EntireRange);
for (size_t Match = InputFile.find("expected-"); Match != StringRef::npos;
Match = InputFile.find("expected-", Match + 1)) {
StringRef MatchStart = InputFile.substr(Match);
const char *DiagnosticLoc = MatchStart.data();
Expectation::Kind ExpectedKind;
{
#define MATRIX_ENTRY(EXPECTATION_SELECTOR, KIND) \
.StartsWith(EXPECTATION_SELECTOR, [&]() { \
ExpectedKind = Expectation::Kind::KIND; \
MatchStart = MatchStart.substr(strlen(EXPECTATION_SELECTOR)); \
})
// clang-format off
llvm::StringSwitch<llvm::function_ref<void(void)>>{MatchStart}
EXPECTATION_MATRIX
.Default([]() {})();
// clang-format on
#undef MATRIX_ENTRY
}
// Skip any whitespace before the {{.
MatchStart = MatchStart.substr(MatchStart.find_first_not_of(" \t"));
auto &diags = SF->getASTContext().Diags;
const size_t TextStartIdx = MatchStart.find("{{");
if (TextStartIdx == StringRef::npos) {
diagnose(diags, MatchStart.data(),
diag::expectation_missing_opening_braces);
continue;
}
const size_t End = MatchStart.find("}}");
if (End == StringRef::npos) {
diagnose(diags, MatchStart.data(),
diag::expectation_missing_closing_braces);
continue;
}
// Check if the next expectation should be in the same line.
StringRef AfterEnd = MatchStart.substr(End + strlen("}}"));
AfterEnd = AfterEnd.substr(AfterEnd.find_first_not_of(" \t"));
const char *ExpectedEnd = AfterEnd.data();
// Strip out the trailing whitespace.
while (isspace(ExpectedEnd[-1]))
--ExpectedEnd;
Expectations.emplace_back(DiagnosticLoc, ExpectedEnd, ExpectedKind,
MatchStart.slice(2, End));
}
return false;
}
bool DependencyVerifier::constructObligations(const SourceFile *SF,
ObligationMap &Obligations) {
auto &Ctx = SF->getASTContext();
Ctx.evaluator.enumerateReferencesInFile(SF, [&](const auto &reference) {
using NodeKind = evaluator::DependencyCollector::Reference::Kind;
switch (reference.kind) {
case NodeKind::Empty:
case NodeKind::Tombstone:
llvm_unreachable("Cannot enumerate dead dependency!");
case NodeKind::PotentialMember: {
auto key = copyQualifiedTypeName(Ctx, reference.subject);
Obligations.insert({Obligation::Key::forPotentialMember(key),
{"", Expectation::Kind::PotentialMember}});
} break;
case NodeKind::UsedMember: {
auto demContext = copyQualifiedTypeName(Ctx, reference.subject);
auto name = reference.name.userFacingName();
auto key = Ctx.AllocateCopy((demContext + "." + name).str());
Obligations.insert(
{Obligation::Key::forMember(key), {key, Expectation::Kind::Member}});
} break;
case NodeKind::Dynamic: {
auto key = Ctx.AllocateCopy(reference.name.userFacingName());
Obligations.insert({Obligation::Key::forDynamicMember(key),
{"", Expectation::Kind::DynamicMember}});
} break;
case NodeKind::TopLevel: {
auto key = Ctx.AllocateCopy(reference.name.userFacingName());
Obligations.insert({Obligation::Key::forProvides(key),
{key, Expectation::Kind::Provides}});
} break;
}
});
return false;
}
bool DependencyVerifier::verifyObligations(
const SourceFile *SF, const std::vector<Expectation> &ExpectedDependencies,
ObligationMap &OM, llvm::StringMap<Expectation> &NegativeExpectations) {
auto &diags = SF->getASTContext().Diags;
for (auto &expectation : ExpectedDependencies) {
if (expectation.Info.Kind == Expectation::Kind::Negative) {
// We'll verify negative expectations separately.
NegativeExpectations.insert({expectation.MessageRange, expectation});
continue;
}
matchExpectationOrFail(
OM, expectation,
[&](Obligation &O) {
switch (expectation.Info.Kind) {
case Expectation::Kind::Negative:
llvm_unreachable("Should have been handled above!");
case Expectation::Kind::Member:
return O.fullfill();
case Expectation::Kind::PotentialMember:
assert(O.getName().empty());
return O.fullfill();
case Expectation::Kind::Provides:
case Expectation::Kind::DynamicMember:
return O.fullfill();
}
llvm_unreachable("Unhandled expectation kind!");
},
[&](const Expectation &e) {
diagnose(diags, e.MessageRange.begin(),
diag::missing_member_dependency,
static_cast<uint8_t>(expectation.Info.Kind), e.MessageRange);
});
}
return false;
}
bool DependencyVerifier::verifyNegativeExpectations(
const SourceFile *SF, ObligationMap &Obligations,
NegativeExpectationMap &NegativeExpectations) {
forEachOwedObligation(Obligations, [&](StringRef key, Obligation &p) {
auto entry = NegativeExpectations.find(key);
if (entry == NegativeExpectations.end()) {
return;
}
auto &expectation = entry->second;
diagnose(SF->getASTContext().Diags, expectation.MessageRange.begin(),
diag::negative_expectation_violated, expectation.MessageRange);
p.fail();
});
return false;
}
bool DependencyVerifier::diagnoseUnfulfilledObligations(
const SourceFile *SF, ObligationMap &Obligations) {
CharSourceRange EntireRange = SM.getRangeForBuffer(*SF->getBufferID());
StringRef InputFile = SM.extractText(EntireRange);
auto &diags = SF->getASTContext().Diags;
auto &Ctx = SF->getASTContext();
forEachOwedObligation(Obligations, [&](StringRef key, Obligation &p) {
// HACK: Diagnosing the end of the buffer will print a carat pointing
// at the file path, but not print any of the buffer's contents, which
// might be misleading.
auto Loc = SourceLoc(llvm::SMLoc::getFromPointer(InputFile.end()));
switch (p.getKind()) {
case Expectation::Kind::Negative:
llvm_unreachable("Obligations may not be negative; only Expectations!");
case Expectation::Kind::Member:
case Expectation::Kind::DynamicMember:
case Expectation::Kind::PotentialMember:
diags
.diagnose(Loc, diag::unexpected_dependency,
static_cast<uint8_t>(p.getKind()), key)
.fixItInsert(Loc, p.renderAsFixit(Ctx));
break;
case Expectation::Kind::Provides:
diags.diagnose(Loc, diag::unexpected_provided_entity, p.getName())
.fixItInsert(Loc, p.renderAsFixit(Ctx));
break;
}
});
return false;
}
bool DependencyVerifier::verifyFile(const SourceFile *SF) {
std::vector<Expectation> ExpectedDependencies;
if (parseExpectations(SF, ExpectedDependencies)) {
return true;
}
ObligationMap Obligations;
if (constructObligations(SF, Obligations)) {
return true;
}
NegativeExpectationMap Negatives;
if (verifyObligations(SF, ExpectedDependencies, Obligations, Negatives)) {
return true;
}
if (verifyNegativeExpectations(SF, Obligations, Negatives)) {
return true;
}
if (diagnoseUnfulfilledObligations(SF, Obligations)) {
return true;
}
return SF->getASTContext().Diags.hadAnyError();
}
//===----------------------------------------------------------------------===//
// MARK: Main entrypoints
//===----------------------------------------------------------------------===//
bool swift::verifyDependencies(SourceManager &SM, ArrayRef<FileUnit *> SFs) {
bool HadError = false;
DependencyVerifier Verifier{SM};
for (const auto *FU : SFs) {
if (const auto *SF = dyn_cast<SourceFile>(FU))
HadError |= Verifier.verifyFile(SF);
}
return HadError;
}
bool swift::verifyDependencies(SourceManager &SM, ArrayRef<SourceFile *> SFs) {
bool HadError = false;
DependencyVerifier Verifier{SM};
for (const auto *SF : SFs) {
HadError |= Verifier.verifyFile(SF);
}
return HadError;
}
#undef EXPECTATION_MATRIX