//===--- 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/Assertions.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::Kind) /// /// Where \c 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 FulfillmentToken prevents misuse of the \c Obligation /// structure by requiring its state to be changed along all program paths. struct FulfillmentToken { friend Obligation; private: FulfillmentToken() = 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; private: Key(StringRef Name, Expectation::Kind Kind) : Name(Name), Kind(Kind) {} 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::getEmptyKey(), static_cast(~0)}; } static inline Obligation::Key getTombstoneKey() { return Obligation::Key{llvm::DenseMapInfo::getTombstoneKey(), static_cast(~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; } FulfillmentToken fulfill() { assert(state == State::Owed && "Cannot fulfill an obligation more than once!"); state = State::Fulfilled; return FulfillmentToken{}; } FulfillmentToken fail() { assert(state == State::Owed && "Cannot fail an obligation more than once!"); state = State::Failed; return FulfillmentToken{}; } }; /// 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>; using NegativeExpectationMap = llvm::StringMap; private: /// These routines return \c true on failure, \c false otherwise. bool parseExpectations(const SourceFile *SF, std::vector &Expectations); bool constructObligations(const SourceFile *SF, ObligationMap &map); bool verifyObligations(const SourceFile *SF, const std::vector &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 fulfill, llvm::function_ref 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 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 InFlightDiagnostic diagnose(DiagnosticEngine &Diags, const char *LocPtr, Diag ID, typename detail::PassArgument::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 &Expectations) { const auto BufferID = SF->getBufferID(); 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>{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->getSelfNominalTypeDecl()); Obligations.insert({Obligation::Key::forPotentialMember(key), {"", Expectation::Kind::PotentialMember}}); } break; case NodeKind::UsedMember: { auto demContext = copyQualifiedTypeName(Ctx, reference.subject->getSelfNominalTypeDecl()); 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 &ExpectedDependencies, ObligationMap &OM, llvm::StringMap &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.fulfill(); case Expectation::Kind::PotentialMember: assert(O.getName().empty()); return O.fulfill(); case Expectation::Kind::Provides: case Expectation::Kind::DynamicMember: return O.fulfill(); } llvm_unreachable("Unhandled expectation kind!"); }, [&](const Expectation &e) { diagnose(diags, e.MessageRange.begin(), diag::missing_member_dependency, static_cast(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(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 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 SFs) { bool HadError = false; DependencyVerifier Verifier{SM}; for (const auto *FU : SFs) { if (const auto *SF = dyn_cast(FU)) HadError |= Verifier.verifyFile(SF); } return HadError; } bool swift::verifyDependencies(SourceManager &SM, ArrayRef SFs) { bool HadError = false; DependencyVerifier Verifier{SM}; for (const auto *SF : SFs) { HadError |= Verifier.verifyFile(SF); } return HadError; } #undef EXPECTATION_MATRIX