//===--- swift-api-digester.cpp - API change detector ---------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 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 // //===----------------------------------------------------------------------===// // swift-api-digester is a test utility to detect source-breaking API changes // during the evolution of a Swift library. The tool works on two phases: // (1) dumping library contents as a JSON file, and (2) comparing two JSON // files textually to report interesting changes. // // During phase (1), the api-digester looks up every declarations inside // a module and outputs a singly-rooted tree that encloses interesting // details of the API level. // // During phase (2), api-digester applies structure-information comparison // algorithms on two given singly root trees, trying to figure out, as // precise as possible, the branches/leaves in the trees that differ from // each other. Further analysis decides whether the changed leaves/branches // can be reflected as source-breaking changes for API users. If they are, // the output of api-digester will include such changes. #include "swift/APIDigester/ModuleAnalyzerNodes.h" #include "swift/APIDigester/ModuleDiagsConsumer.h" #include "swift/AST/DiagnosticsModuleDiffer.h" #include "swift/Basic/Assertions.h" #include "swift/Basic/Defer.h" #include "swift/Basic/Platform.h" #include "swift/Frontend/PrintingDiagnosticConsumer.h" #include "swift/Frontend/SerializedDiagnosticConsumer.h" #include "swift/IDE/APIDigesterData.h" #include "swift/Option/Options.h" #include "swift/Parse/ParseVersion.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/VirtualOutputBackends.h" #include "llvm/Support/raw_ostream.h" #include using namespace swift; using namespace ide; using namespace api; using namespace swift::options; namespace { enum class ActionType { None, DumpSDK, MigratorGen, DiagnoseSDKs, // The following two are for testing purposes DeserializeDiffItems, DeserializeSDK, GenerateNameCorrectionTemplate, FindUsr, GenerateEmptyBaseline, }; } // end anonymous namespace namespace { using swift::ide::api::KnownProtocolKind; // A node matcher will traverse two trees of SDKNode and find matched nodes struct NodeMatcher { virtual void match() = 0; virtual ~NodeMatcher() = default; }; // During the matching phase, any matched node will be reported using this API. // For update Node left = {Node before change} Right = {Node after change}; // For added Node left = {NilNode} Right = {Node after change}; // For removed Node left = {Node before change} Right = {NilNode} struct MatchedNodeListener { virtual void foundMatch(NodePtr Left, NodePtr Right, NodeMatchReason Reason) = 0; virtual ~MatchedNodeListener() = default; }; static void singleMatch(SDKNode* Left, SDKNode *Right, MatchedNodeListener &Listener) { // Both null, be forgiving. if (!Left && !Right) return; // If both are valid and identical to each other, we don't need to match them. if (Left && Right && *Left == *Right) return; if (!Left || !Right) Listener.foundMatch(Left, Right, Left ? NodeMatchReason::Removed : NodeMatchReason::Added); else Listener.foundMatch(Left, Right, NodeMatchReason::Sequential); } // Given two NodeVector, this matches SDKNode by the order of their appearance // in the respective NodeVector. We use this in the order-sensitive cases, such // as parameters in a function decl. class SequentialNodeMatcher : public NodeMatcher { ArrayRef Left; ArrayRef Right; MatchedNodeListener &Listener; public: SequentialNodeMatcher(ArrayRef Left, ArrayRef Right, MatchedNodeListener &Listener) : Left(Left), Right(Right), Listener(Listener) {} void match() override { for (unsigned long i = 0; i < std::max(Left.size(), Right.size()); i ++) { auto L = i < Left.size() ? Left[i] : nullptr; auto R = i < Right.size() ? Right[i] : nullptr; singleMatch(L, R, Listener); } } }; struct NodeMatch { NodePtr Left; NodePtr Right; }; class BestMatchMatcher : public NodeMatcher { NodeVector &Left; NodeVector &Right; llvm::function_ref CanMatch; llvm::function_ref IsFirstMatchBetter; NodeMatchReason Reason; MatchedNodeListener &Listener; llvm::SmallPtrSet MatchedRight; bool internalCanMatch(NodePtr L, NodePtr R) { return MatchedRight.count(R) == 0 && CanMatch(L, R); } std::optional findBestMatch(NodePtr Pin, NodeVector &Candidates) { std::optional Best; for (auto Can : Candidates) { if (!internalCanMatch(Pin, Can)) continue; if (!Best.has_value() || IsFirstMatchBetter({Pin, Can}, {Pin, Best.value()})) Best = Can; } return Best; } public: BestMatchMatcher(NodeVector &Left, NodeVector &Right, llvm::function_ref CanMatch, llvm::function_ref IsFirstMatchBetter, NodeMatchReason Reason, MatchedNodeListener &Listener) : Left(Left), Right(Right), CanMatch(CanMatch), IsFirstMatchBetter(IsFirstMatchBetter), Reason(Reason), Listener(Listener){} void match() override { for (auto L : Left) { if (auto Best = findBestMatch(L, Right)) { MatchedRight.insert(Best.value()); Listener.foundMatch(L, Best.value(), Reason); } } } }; class RemovedAddedNodeMatcher : public NodeMatcher, public MatchedNodeListener { NodeVector &Removed; NodeVector &Added; MatchedNodeListener &Listener; NodeVector RemovedMatched; NodeVector AddedMatched; void handleUnmatch(NodeVector &Matched, NodeVector &All, bool Left) { for (auto A : All) { if (llvm::is_contained(Matched, A)) continue; if (Left) Listener.foundMatch(A, nullptr, NodeMatchReason::Removed); else Listener.foundMatch(nullptr, A, NodeMatchReason::Added); } } bool detectFuncToProperty(SDKNode *R, SDKNode *A) { if (R->getKind() == SDKNodeKind::DeclFunction) { if (A->getKind() == SDKNodeKind::DeclVar) { if (A->getName().compare_insensitive(R->getName()) == 0) { R->annotate(NodeAnnotation::GetterToProperty); } else if (R->getName().starts_with("get") && R->getName().substr(3).compare_insensitive(A->getName()) == 0) { R->annotate(NodeAnnotation::GetterToProperty); } else if (R->getName().starts_with("set") && R->getName().substr(3).compare_insensitive(A->getName()) == 0) { R->annotate(NodeAnnotation::SetterToProperty); } else { return false; } R->annotate(NodeAnnotation::PropertyName, A->getPrintedName()); foundMatch(R, A, NodeMatchReason::FuncToProperty); return true; } } return false; } static bool isAnonymousEnum(SDKNodeDecl *N) { return N->getKind() == SDKNodeKind::DeclVar && N->getUsr().starts_with("c:@Ea@"); } static bool isNominalEnum(SDKNodeDecl *N) { return N->getKind() == SDKNodeKind::DeclType && N->getUsr().starts_with("c:@E@"); } static std::optional getLastPartOfUsr(SDKNodeDecl *N) { auto LastPartIndex = N->getUsr().find_last_of('@'); if (LastPartIndex == StringRef::npos) return std::nullopt; return N->getUsr().substr(LastPartIndex + 1); } bool detectTypeAliasChange(SDKNodeDecl *R, SDKNodeDecl *A) { if (R->getPrintedName() != A->getPrintedName()) return false; if (R->getKind() == SDKNodeKind::DeclType && A->getKind() == SDKNodeKind::DeclTypeAlias) { foundMatch(R, A, NodeMatchReason::TypeToTypeAlias); return true; } else { return false; } } bool detectModernizeEnum(SDKNodeDecl *R, SDKNodeDecl *A) { if (!isAnonymousEnum(R) || !isNominalEnum(A)) return false; auto LastPartOfR = getLastPartOfUsr(R); if (!LastPartOfR) return false; for (auto Child : A->getChildren()) { if (auto VC = dyn_cast(Child)) { auto LastPartOfA = getLastPartOfUsr(VC); if (LastPartOfA && LastPartOfR.value() == LastPartOfA.value()) { std::string FullName = (llvm::Twine(A->getName()) + "." + Child->getName()).str(); R->annotate(NodeAnnotation::ModernizeEnum, R->getSDKContext().buffer(FullName)); foundMatch(R, A, NodeMatchReason::ModernizeEnum); return true; } } } return false; } bool detectSameAnonymousEnum(SDKNodeDecl *R, SDKNodeDecl *A) { if (!isAnonymousEnum(R) || !isAnonymousEnum(A)) return false; auto LastR = getLastPartOfUsr(R); auto LastA = getLastPartOfUsr(A); if (LastR && LastA && LastR.value() == LastA.value()) { foundMatch(R, A, NodeMatchReason::Name); return true; } return false; } static bool isNameTooSimple(StringRef N) { static std::vector SimpleNames = {"unit", "data", "log", "coding", "url", "name", "date", "datecomponents", "notification", "urlrequest", "personnamecomponents", "measurement", "dateinterval", "indexset"}; return std::find(SimpleNames.begin(), SimpleNames.end(), N) != SimpleNames.end(); } static bool isSimilarName(StringRef L, StringRef R) { auto LL = L.lower(); auto RR = R.lower(); if (isNameTooSimple(LL) || isNameTooSimple(RR)) return false; if (((StringRef)LL).starts_with(RR) || ((StringRef)RR).starts_with(LL)) return true; if (((StringRef)LL).starts_with((llvm::Twine("ns") + RR).str()) || ((StringRef)RR).starts_with((llvm::Twine("ns") + LL).str())) return true; if (((StringRef)LL).ends_with(RR) || ((StringRef)RR).ends_with(LL)) return true; return false; } /// Whether two decls of different decl kinds can be considered as rename. static bool isDeclKindCrossable(DeclKind DK1, DeclKind DK2, bool First) { if (DK1 == DK2) return true; if (DK1 == DeclKind::Var && DK2 == DeclKind::EnumElement) return true; return First && isDeclKindCrossable(DK2, DK1, false); } static bool isRename(NodePtr L, NodePtr R) { if (L->getKind() != R->getKind()) return false; if (isa(L)) return false; if (auto LD = dyn_cast(L)) { auto *RD = R->getAs(); return isDeclKindCrossable(LD->getDeclKind(), RD->getDeclKind(), true) && isSimilarName(LD->getName(), RD->getName()); } return false; } static bool isBetterMatch(NodeMatch Match1, NodeMatch Match2) { assert(Match1.Left == Match2.Left); auto Left = Match1.Left; auto *M1Right = Match1.Right->getAs(); auto *M2Right = Match2.Right->getAs(); // Consider non-deprecated nodes better matches. auto Dep1 = M1Right->isDeprecated(); auto Dep2 = M2Right->isDeprecated(); if (Dep1 ^ Dep2) { return Dep2; } // If two names are identical, measure whose printed names is closer. if (M1Right->getName() == M2Right->getName()) { return M1Right->getPrintedName().edit_distance(Left->getPrintedName()) < M2Right->getPrintedName().edit_distance(Left->getPrintedName()); } #define DIST(A, B) (std::max(A, B) - std::min(A, B)) return DIST(Left->getName().size(), Match1.Right->getName().size()) < DIST(Left->getName().size(), Match2.Right->getName().size()); #undef DIST } void foundMatch(NodePtr R, NodePtr A, NodeMatchReason Reason) override { Listener.foundMatch(R, A, Reason); RemovedMatched.push_back(R); AddedMatched.push_back(A); } public: RemovedAddedNodeMatcher(NodeVector &Removed, NodeVector &Added, MatchedNodeListener &Listener) : Removed(Removed), Added(Added), Listener(Listener) {} void match() override { auto IsDecl = [](NodePtr P) { return isa(P); }; for (auto R : SDKNodeVectorViewer(Removed, IsDecl)) { for (auto A : SDKNodeVectorViewer(Added, IsDecl)) { auto RD = R->getAs(); auto AD = A->getAs(); if (detectFuncToProperty(RD, AD) || detectModernizeEnum(RD, AD) || detectSameAnonymousEnum(RD, AD) || detectTypeAliasChange(RD, AD)) { break; } } } // Rename detection starts. NodeVector RenameLeft; NodeVector RenameRight; for (auto Remain : Removed) { if (!llvm::is_contained(RemovedMatched, Remain)) RenameLeft.push_back(Remain); } for (auto Remain : Added) { if (!llvm::is_contained(AddedMatched, Remain)) RenameRight.push_back(Remain); } BestMatchMatcher RenameMatcher(RenameLeft, RenameRight, isRename, isBetterMatch, NodeMatchReason::Name, *this); RenameMatcher.match(); // Rename detection ends. handleUnmatch(RemovedMatched, Removed, true); handleUnmatch(AddedMatched, Added, false); } }; // Given two NodeVector, this matches SDKNode by the their names; only Nodes with // the identical names will be matched. We use this in name-sensitive but // order-insensitive cases, such as matching types in a module. class SameNameNodeMatcher : public NodeMatcher { ArrayRef Left; ArrayRef Right; MatchedNodeListener &Listener; enum class NameMatchKind { USR, PrintedName, PrintedNameAndUSR, }; static bool isUSRSame(SDKNode *L, SDKNode *R) { auto *LD = dyn_cast(L); auto *RD = dyn_cast(R); if (!LD || !RD) return false; return LD->getUsr() == RD->getUsr(); } // Given two SDK nodes, figure out the reason for why they have the same name. std::optional getNameMatchKind(SDKNode *L, SDKNode *R) { if (L->getKind() != R->getKind()) return std::nullopt; auto NameEqual = L->getPrintedName() == R->getPrintedName(); auto UsrEqual = isUSRSame(L, R); if (NameEqual && UsrEqual) return NameMatchKind::PrintedNameAndUSR; else if (NameEqual) return NameMatchKind::PrintedName; else if (UsrEqual) return NameMatchKind::USR; else return std::nullopt; } struct NameMatchCandidate { SDKNode *Node; NameMatchKind Kind; }; // Get the priority for the favored name match kind. Favored name match kind // locals before less favored ones. ArrayRef getNameMatchKindPriority(SDKNodeKind Kind) { if (Kind == SDKNodeKind::DeclFunction) { static NameMatchKind FuncPriority[] = { NameMatchKind::PrintedNameAndUSR, NameMatchKind::USR, NameMatchKind::PrintedName }; return FuncPriority; } else { static NameMatchKind OtherPriority[] = { NameMatchKind::PrintedNameAndUSR, NameMatchKind::PrintedName, NameMatchKind::USR }; return OtherPriority; } } // Given a list and a priority, find the best matched candidate SDK node. SDKNode* findBestNameMatch(ArrayRef Candidates, ArrayRef Kinds) { for (auto Kind : Kinds) for (auto &Can : Candidates) if (Kind == Can.Kind) return Can.Node; return nullptr; } public: SameNameNodeMatcher(ArrayRef Left, ArrayRef Right, MatchedNodeListener &Listener) : Left(Left), Right(Right), Listener(Listener) {} void match() override ; }; void SameNameNodeMatcher::match() { NodeVector MatchedRight; NodeVector Removed; NodeVector Added; for (auto *LN : Left) { // This collects all the candidates that can match with LN. std::vector Candidates; for (auto *RN : Right) { // If RN has matched before, ignore it. if (llvm::is_contained(MatchedRight, RN)) continue; // If LN and RN have the same name for some reason, keep track of RN. if (auto Kind = getNameMatchKind(LN, RN)) Candidates.push_back({RN, Kind.value()}); } // Try to find the best match among all the candidates by the priority name // match kind list. if (auto Match = findBestNameMatch(Candidates, getNameMatchKindPriority(LN->getKind()))) { Listener.foundMatch(LN, Match, NodeMatchReason::Name); MatchedRight.push_back(Match); } else { Removed.push_back(LN); } } for (auto &R : Right) { if (!llvm::is_contained(MatchedRight, R)) { Added.push_back(R); } } RemovedAddedNodeMatcher RAMatcher(Removed, Added, Listener); RAMatcher.match(); } // The recursive version of sequential matcher. We do not only match two vectors // of NodePtr but also their descendents. class SequentialRecursiveMatcher : public NodeMatcher { NodePtr &Left; NodePtr &Right; MatchedNodeListener &Listener; void matchInternal(NodePtr L, NodePtr R) { Listener.foundMatch(L, R, NodeMatchReason::Sequential); if (!L || !R) return; for (unsigned I = 0; I < std::max(L->getChildrenCount(), R->getChildrenCount()); ++ I) { auto Left = I < L->getChildrenCount() ? L->childAt(I) : nullptr; auto Right = I < R->getChildrenCount() ? R->childAt(I): nullptr; matchInternal(Left, Right); } } public: SequentialRecursiveMatcher(NodePtr &Left, NodePtr &Right, MatchedNodeListener &Listener) : Left(Left), Right(Right), Listener(Listener) {} void match() override { matchInternal(Left, Right); } }; // This is the interface of all passes on the given trees rooted at Left and Right. class SDKTreeDiffPass { public: virtual void pass(NodePtr Left, NodePtr Right) = 0; virtual ~SDKTreeDiffPass() {} }; }// End of anonymous namespace namespace { static bool isMissingDeclAcceptable(const SDKNodeDecl *D) { // Don't complain about removing importation of SwiftOnoneSupport. if (D->getKind() == SDKNodeKind::DeclImport) { return true; } return false; } static void diagnoseRemovedDecl(const SDKNodeDecl *D) { if (D->getSDKContext().checkingABI()) { // Don't complain about removing @_alwaysEmitIntoClient if we are checking ABI. // We shouldn't include these decls in the ABI baseline file. This line is // added so the checker is backward compatible. if (D->hasDeclAttribute(DeclAttrKind::AlwaysEmitIntoClient)) return; } auto &Ctx = D->getSDKContext(); // Don't diagnose removal of deprecated APIs. if (Ctx.getOpts().SkipRemoveDeprecatedCheck && D->isDeprecated()) return; if (isMissingDeclAcceptable(D)) { return; } D->emitDiag(SourceLoc(), diag::removed_decl, false); } // This is first pass on two given SDKNode trees. This pass removes the common part // of two versions of SDK, leaving only the changed part. class PrunePass : public MatchedNodeListener, public SDKTreeDiffPass { static void removeCommon(NodeVector &Left, NodeVector &Right) { NodeVector LeftMinusRight, RightMinusLeft; nodeSetDifference(Left, Right, LeftMinusRight, RightMinusLeft); Left = LeftMinusRight; Right = RightMinusLeft; } static void removeCommonChildren(NodePtr Left, NodePtr Right) { removeCommon(Left->getChildren(), Right->getChildren()); } SDKContext &Ctx; UpdatedNodesMap &UpdateMap; llvm::StringSet<> ProtocolReqAllowlist; SDKNodeRoot *LeftRoot; SDKNodeRoot *RightRoot; bool DebugMapping; static void printSpaces(llvm::raw_ostream &OS, SDKNode *N) { assert(N); StringRef Space = " "; // Accessor doesn't have parent. if (auto *AC = dyn_cast(N)) { OS << Space; printSpaces(OS, AC->getStorage()); return; } for (auto P = N; !isa(P); P = P->getParent()) OS << Space; } static void debugMatch(SDKNode *Left, SDKNode *Right, NodeMatchReason Reason, llvm::raw_ostream &OS) { if (Left && !isa(Left)) return; if (Right && !isa(Right)) return; StringRef Arrow = " <--------> "; switch (Reason) { case NodeMatchReason::Added: printSpaces(OS, Right); OS << "" << Arrow << Right->getPrintedName() << "\n"; return; case NodeMatchReason::Removed: printSpaces(OS, Left); OS << Left->getPrintedName() << Arrow << "\n"; return; default: printSpaces(OS, Left); OS << Left->getPrintedName() << Arrow << Right->getPrintedName() << "\n"; return; } } static StringRef getParentProtocolName(SDKNode *Node) { if (auto *Acc = dyn_cast(Node)) { Node = Acc->getStorage(); } return Node->getParent()->getAs()->getFullyQualifiedName(); } public: PrunePass(SDKContext &Ctx, bool DebugMapping) : Ctx(Ctx), UpdateMap(Ctx.getNodeUpdateMap()), DebugMapping(DebugMapping) {} PrunePass(SDKContext &Ctx, llvm::StringSet<> prAllowlist, bool DebugMapping) : Ctx(Ctx), UpdateMap(Ctx.getNodeUpdateMap()), ProtocolReqAllowlist(std::move(prAllowlist)), DebugMapping(DebugMapping) {} void diagnoseMissingAvailable(SDKNodeDecl *D) { // For extensions of external types, we diagnose individual member's missing // available attribute instead of the extension itself. // The reason is we may merge several extensions into a single one; some // attributes are missing. if (auto *DT = dyn_cast(D)) { if (DT->isExtension()) { for(auto MD: DT->getChildren()) { diagnoseMissingAvailable(cast(MD)); } return; } } // Diagnose the missing of @available attributes. // Decls with @_alwaysEmitIntoClient aren't required to have an // @available attribute. if (!Ctx.getOpts().SkipOSCheck && DeclAttribute::canAttributeAppearOnDeclKind(DeclAttrKind::Available, D->getDeclKind()) && !D->getIntroducingVersion().hasOSAvailability() && !D->hasDeclAttribute(DeclAttrKind::AlwaysEmitIntoClient) && !D->hasDeclAttribute(DeclAttrKind::Marker)) { D->emitDiag(D->getLoc(), diag::new_decl_without_intro); } } void foundMatch(NodePtr Left, NodePtr Right, NodeMatchReason Reason) override { if (DebugMapping) debugMatch(Left, Right, Reason, llvm::errs()); switch (Reason) { case NodeMatchReason::Added: assert(!Left); Right->annotate(NodeAnnotation::Added); if (Ctx.checkingABI()) { // Any order-important decl added to a non-resilient type breaks ABI. if (auto *D = dyn_cast(Right)) { if (D->hasFixedBinaryOrder()) { D->emitDiag(D->getLoc(), diag::decl_added); } diagnoseMissingAvailable(D); } } // Complain about added protocol requirements if (auto *D = dyn_cast(Right)) { if (D->isNonOptionalProtocolRequirement()) { bool ShouldComplain = !D->isOverriding(); // We should allow added associated types with default. if (auto ATD = dyn_cast(D)) { if (ATD->getDefault()) ShouldComplain = false; } if (ShouldComplain && ProtocolReqAllowlist.count(getParentProtocolName(D))) { // Ignore protocol requirement additions if the protocol has been added // to the allowlist. ShouldComplain = false; } if (ShouldComplain) { // Providing a default implementation via a protocol extension for // a protocol requirement is both ABI and API safe. if (auto *PD = dyn_cast(D->getParent())) { for (auto *SIB: PD->getChildren()) { if (auto *SIBD = dyn_cast(SIB)) { if (SIBD->isFromExtension() && SIBD->getPrintedName() == D->getPrintedName()) { ShouldComplain = false; break; } } } } } if (ShouldComplain) D->emitDiag(D->getLoc(), diag::protocol_req_added); } } // Diagnose an inherited protocol has been added. if (auto *Conf = dyn_cast(Right)) { auto *TD = Conf->getNominalTypeDecl(); if (TD->isProtocol()) { TD->emitDiag(TD->getLoc(), diag::conformance_added, Conf->getName()); } else { // Adding conformance to an existing type can be ABI breaking. if (Ctx.checkingABI() && !LeftRoot->getDescendantsByUsr(Conf->getUsr()).empty()) { TD->emitDiag(TD->getLoc(), diag::existing_conformance_added, Conf->getName()); } } } if (auto *CD = dyn_cast(Right)) { if (auto *TD = dyn_cast(Right->getParent())) { if (TD->isOpen() && CD->getInitKind() == CtorInitializerKind::Designated) { // If client's subclass provides an implementation of all of its superclass designated // initializers, it automatically inherits all of the superclass convenience initializers. // This means if a new designated init is added to the base class, the inherited // convenience init may be missing and cause breakage. CD->emitDiag(CD->getLoc(), diag::desig_init_added); } } } // Adding an enum case is source-breaking. if (!Ctx.checkingABI()) { if (auto *Var = dyn_cast(Right)) { if (Var->getDeclKind() == DeclKind::EnumElement) { if (Var->getParent()->getAs()->isEnumExhaustive()) { Var->emitDiag(Var->getLoc(), diag::enum_case_added); } } } } return; case NodeMatchReason::Removed: assert(!Right); Left->annotate(NodeAnnotation::Removed); if (auto *LT = dyn_cast(Left)) { if (auto *AT = dyn_cast(LT->getParent())) { AT->emitDiag(SourceLoc(), diag::default_associated_type_removed, LT->getPrintedName()); } } // Diagnose a protocol conformance has been removed. if (auto *Conf = dyn_cast(Left)) { auto *TD = Conf->getNominalTypeDecl(); TD->emitDiag(SourceLoc(), diag::conformance_removed, Conf->getName(), TD->isProtocol()); } if (auto *Acc = dyn_cast(Left)) { diagnoseRemovedDecl(Acc); } return; case NodeMatchReason::FuncToProperty: case NodeMatchReason::ModernizeEnum: case NodeMatchReason::TypeToTypeAlias: Left->annotate(NodeAnnotation::Removed); Right->annotate(NodeAnnotation::Added); return; case NodeMatchReason::Root: case NodeMatchReason::Name: case NodeMatchReason::Sequential: break; } assert(Left && Right); Left->annotate(NodeAnnotation::Updated); Right->annotate(NodeAnnotation::Updated); // Push the updated node to the map for future reference. UpdateMap.insert(Left, Right); Left->diagnose(Right); if (Left->getKind() != Right->getKind()) { assert(isa(Left) && isa(Right) && "only type nodes can match across kinds."); return; } assert(Left->getKind() == Right->getKind()); SDKNodeKind Kind = Left->getKind(); assert(Kind == SDKNodeKind::Root || *Left != *Right); switch(Kind) { case SDKNodeKind::DeclType: { // Remove common conformances and diagnose conformance changes. auto LConf = cast(Left)->getConformances(); auto RConf = cast(Right)->getConformances(); removeCommon(LConf, RConf); SameNameNodeMatcher(LConf, RConf, *this).match(); LLVM_FALLTHROUGH; } case SDKNodeKind::Conformance: case SDKNodeKind::Root: { // If the matched nodes are both modules, remove the contained // type decls that are identical. If the matched nodes are both type decls, // remove the contained function decls that are identical. removeCommonChildren(Left, Right); SameNameNodeMatcher SNMatcher(Left->getChildren(), Right->getChildren(), *this); SNMatcher.match(); break; } case SDKNodeKind::TypeWitness: case SDKNodeKind::DeclOperator: case SDKNodeKind::DeclAssociatedType: case SDKNodeKind::DeclFunction: case SDKNodeKind::DeclAccessor: case SDKNodeKind::DeclConstructor: case SDKNodeKind::DeclTypeAlias: case SDKNodeKind::DeclImport: case SDKNodeKind::TypeFunc: case SDKNodeKind::TypeNominal: case SDKNodeKind::TypeAlias: case SDKNodeKind::DeclMacro: { // If matched nodes are both function/var/TypeAlias decls, mapping their // parameters sequentially. SequentialNodeMatcher SNMatcher(Left->getChildren(), Right->getChildren(), *this); SNMatcher.match(); break; } case SDKNodeKind::DeclSubscript: { auto *LSub = dyn_cast(Left); auto *RSub = dyn_cast(Right); SequentialNodeMatcher(LSub->getChildren(), RSub->getChildren(), *this).match(); #define ACCESSOR(ID, KEYWORD) \ singleMatch(LSub->getAccessor(AccessorKind::ID), \ RSub->getAccessor(AccessorKind::ID), *this); #include "swift/AST/AccessorKinds.def" break; } case SDKNodeKind::DeclVar: { auto *LVar = dyn_cast(Left); auto *RVar = dyn_cast(Right); // Match property type. singleMatch(LVar->getType(), RVar->getType(), *this); #define ACCESSOR(ID, KEYWORD) \ singleMatch(LVar->getAccessor(AccessorKind::ID), \ RVar->getAccessor(AccessorKind::ID), *this); #include "swift/AST/AccessorKinds.def" break; } } } void pass(NodePtr Left, NodePtr Right) override { LeftRoot = Left->getAs(); RightRoot = Right->getAs(); foundMatch(Left, Right, NodeMatchReason::Root); } }; // Class to build up a diff of structurally different nodes, based on the given // USR map for the left (original) side of the diff, based on parent types. class TypeMemberDiffFinder : public SDKNodeVisitor { friend class SDKNode; // for visit() SDKNodeRoot *diffAgainst; // Vector of {givenNodePtr, diffAgainstPtr} NodePairVector TypeMemberDiffs; void visit(NodePtr node) override { // Skip nodes that we don't have a correlate for auto declNode = dyn_cast(node); if (!declNode) return; auto usr = declNode->getUsr(); auto &usrName = usr; // If we can find no nodes in the other tree with the same usr, abort. auto candidates = diffAgainst->getDescendantsByUsr(usrName); if (candidates.empty()) return; // If any of the candidates has the same kind and name with the node, we // shouldn't continue. for (auto Can : candidates) { if (Can->getKind() == declNode->getKind() && Can->getAs()->getFullyQualifiedName() == declNode->getFullyQualifiedName()) return; } auto diffNode = candidates.front(); assert(node && diffNode && "nullptr visited?"); auto nodeParent = node->getParent(); auto diffParent = diffNode->getParent(); assert(nodeParent && diffParent && "trying to check Root?"); // Move from global variable to a member variable. if (nodeParent->getKind() == SDKNodeKind::DeclType && diffParent->getKind() == SDKNodeKind::Root) TypeMemberDiffs.insert({diffNode, node}); // Move from a member variable to global variable. if (nodeParent->getKind() == SDKNodeKind::Root && diffParent->getKind() == SDKNodeKind::DeclType) TypeMemberDiffs.insert({diffNode, node}); // Move from a member variable to another member variable if (nodeParent->getKind() == SDKNodeKind::DeclType && diffParent->getKind() == SDKNodeKind::DeclType && declNode->isStatic()) TypeMemberDiffs.insert({diffNode, node}); // Move from a getter/setter function to a property else if (node->getKind() == SDKNodeKind::DeclAccessor && diffNode->getKind() == SDKNodeKind::DeclFunction && node->isNameValid()) { diffNode->annotate(NodeAnnotation::Rename); diffNode->annotate(NodeAnnotation::RenameOldName, diffNode->getPrintedName()); diffNode->annotate(NodeAnnotation::RenameNewName, node->getParent()->getPrintedName()); } } public: TypeMemberDiffFinder(SDKNodeRoot *diffAgainst): diffAgainst(diffAgainst) {} void findDiffsFor(NodePtr ptr) { SDKNode::preorderVisit(ptr, *this); } const NodePairVector &getDiffs() const { return TypeMemberDiffs; } void dump(llvm::raw_ostream &) const; void dump() const { dump(llvm::errs()); } private: TypeMemberDiffFinder(const TypeMemberDiffFinder &) = delete; TypeMemberDiffFinder &operator=(const TypeMemberDiffFinder &) = delete; }; /// This is to find type alias of raw types being changed to RawRepresentable. /// e.g. AttributeName was a typealias of String in the old SDK however it becomes /// a RawRepresentable struct in the new SDK. /// This happens typically when we use apinotes to preserve API stability by /// using SwiftWrapper:none in the old SDK. class TypeAliasDiffFinder: public SDKNodeVisitor { SDKNodeRoot *leftRoot; SDKNodeRoot *rightRoot; NodeMap &result; static bool checkTypeMatch(const SDKNodeType* aliasType, const SDKNodeType* rawType) { StringRef Left = aliasType->getPrintedName(); StringRef Right = rawType->getPrintedName(); if (Left == "NSString" && Right == "String") return true; if (Left == "String" && Right == "String") return true; if (Left == "Int" && Right == "Int") return true; if (Left == "UInt" && Right == "UInt") return true; return false; } void visit(NodePtr node) override { auto alias = dyn_cast(node); if (!alias) return; const SDKNodeType* aliasType = alias->getUnderlyingType(); for (auto *counter: rightRoot->getDescendantsByUsr(alias->getUsr())) { if (auto DT = dyn_cast(counter)) { if (auto *rawType = DT->getRawValueType()) { if (checkTypeMatch(aliasType, rawType)) { result.insert({alias, DT}); return; } } } } } public: TypeAliasDiffFinder(SDKNodeRoot *leftRoot, SDKNodeRoot *rightRoot, NodeMap &result): leftRoot(leftRoot), rightRoot(rightRoot), result(result) {} void search() { SDKNode::preorderVisit(leftRoot, *this); } }; // Given a condition, search whether a node satisfies that condition exists // in a tree. class SearchVisitor : public SDKNodeVisitor { bool isFound = false; llvm::function_ref Predicate; public: SearchVisitor(llvm::function_ref Predicate) : Predicate(Predicate) {} void visit(NodePtr Node) override { isFound |= Predicate(Node); } bool search(NodePtr Node) { SDKNode::preorderVisit(Node, *this); return isFound; } }; class InterfaceTypeChangeDetector { bool IsVisitingLeft; #define ANNOTATE(Node, Counter, X, Y) \ auto ToAnnotate = IsVisitingLeft ? Node : Counter; \ ToAnnotate->annotate(IsVisitingLeft ? X : Y); bool detectWrapOptional(SDKNodeType *Node, SDKNodeType *Counter) { if (Node->getTypeKind() != KnownTypeKind::Optional && Node->getTypeKind() != KnownTypeKind::ImplicitlyUnwrappedOptional && Counter->getTypeKind() == KnownTypeKind::Optional && *Node == *Counter->getOnlyChild()) { ANNOTATE(Node, Counter, NodeAnnotation::WrapOptional, NodeAnnotation::UnwrapOptional) return true; } return false; } bool detectWrapImplicitOptional(SDKNodeType *Node, SDKNodeType *Counter) { if (Node->getTypeKind() != KnownTypeKind::Optional && Node->getTypeKind() != KnownTypeKind::ImplicitlyUnwrappedOptional && Counter->getTypeKind() == KnownTypeKind::ImplicitlyUnwrappedOptional && *Node == *Counter->getOnlyChild()) { ANNOTATE(Node, Counter, NodeAnnotation::WrapImplicitOptional, NodeAnnotation::UnwrapOptional) return true; } return false; } bool detectOptionalUpdate(SDKNodeType *Node, SDKNodeType *Counter) { if (Node->getTypeKind() == KnownTypeKind::Optional && Counter->getTypeKind() == KnownTypeKind::ImplicitlyUnwrappedOptional && *Node->getOnlyChild() == *Counter->getOnlyChild()) { ANNOTATE(Node, Counter, NodeAnnotation::OptionalToImplicitOptional, NodeAnnotation::ImplicitOptionalToOptional) return true; } return false; } bool detectUnmanagedUpdate(SDKNodeType *Node, SDKNodeType *Counter) { if (IsVisitingLeft && Node->getTypeKind() == KnownTypeKind::Unmanaged && Counter->getTypeKind() != KnownTypeKind::Unmanaged && *Node->getOnlyChild() == *Counter) { Node->annotate(NodeAnnotation::UnwrapUnmanaged); return true; } return false; } #undef ANNOTATE bool detectTypeRewritten(SDKNodeType *Node, SDKNodeType *Counter) { if (IsVisitingLeft && Node->getPrintedName() != Counter->getPrintedName() && (Node->getName() != Counter->getName() || Node->getChildrenCount() != Counter->getChildrenCount())) { Node->annotate(NodeAnnotation::TypeRewritten); Node->annotate(NodeAnnotation::TypeRewrittenLeft, Node->getPrintedName()); Node->annotate(NodeAnnotation::TypeRewrittenRight, Counter->getPrintedName()); return true; } return false; } static bool isRawType(const SDKNodeType *T, StringRef &Raw) { if (auto Alias = dyn_cast(T)) { // In case this type is an alias of the raw type. return isRawType(Alias->getUnderlyingType(), Raw); } switch(T->getTypeKind()) { case KnownTypeKind::String: case KnownTypeKind::Int: Raw = T->getName(); return true; default: return false; } } static StringRef getStringRepresentableChange(SDKNode *L, SDKNode *R, StringRef &Raw) { if (!isRawType(L->getAs(), Raw)) return StringRef(); auto* RKey = dyn_cast(R); if (!RKey) return StringRef(); if (Raw.empty()) return StringRef(); auto Results = RKey->getRootNode()->getDescendantsByUsr(RKey->getUsr()); if (Results.empty()) return StringRef(); if (auto DT = dyn_cast(Results.front())) { if (DT->isConformingTo(KnownProtocolKind::RawRepresentable)) { return DT->getFullyQualifiedName(); } } return StringRef(); } static StringRef detectDictionaryKeyChangeInternal(SDKNodeType *L, SDKNodeType *R, StringRef &Raw) { if (L->getTypeKind() != KnownTypeKind::Dictionary || R->getTypeKind() != KnownTypeKind::Dictionary) return StringRef(); auto *Left = dyn_cast(L); auto *Right = dyn_cast(R); assert(Left && Right); assert(Left->getChildrenCount() == 2); assert(Right->getChildrenCount() == 2); return getStringRepresentableChange(*Left->getChildBegin(), *Right->getChildBegin(), Raw); } bool detectDictionaryKeyChange(SDKNodeType *L, SDKNodeType *R) { // We only care if this the top-level type node. if (!L->isTopLevelType() || !R->isTopLevelType()) return false; StringRef Raw; StringRef KeyChangedTo; bool HasOptional = L->getTypeKind() == KnownTypeKind::Optional && R->getTypeKind() == KnownTypeKind::Optional; if (HasOptional) { // Detect [String: Any]? to [StringRepresentableStruct: Any]? Change KeyChangedTo = detectDictionaryKeyChangeInternal(L->getOnlyChild()->getAs(), R->getOnlyChild()->getAs(), Raw); } else { // Detect [String: Any] to [StringRepresentableStruct: Any] Change KeyChangedTo = detectDictionaryKeyChangeInternal(L, R, Raw); } if (!KeyChangedTo.empty()) { if (IsVisitingLeft) { L->annotate(HasOptional ? NodeAnnotation::OptionalDictionaryKeyUpdate : NodeAnnotation::DictionaryKeyUpdate); L->annotate(NodeAnnotation::RawTypeLeft, Raw); L->annotate(NodeAnnotation::RawTypeRight, KeyChangedTo); } else { R->annotate(HasOptional ? NodeAnnotation::RevertOptionalDictionaryKeyUpdate : NodeAnnotation::RevertDictionaryKeyUpdate); R->annotate(NodeAnnotation::RawTypeLeft, KeyChangedTo); R->annotate(NodeAnnotation::RawTypeRight, Raw); } return true; } return false; } static StringRef detectArrayMemberChangeInternal(SDKNodeType *L, SDKNodeType *R, StringRef &Raw) { if (L->getTypeKind() != KnownTypeKind::Array || R->getTypeKind() != KnownTypeKind::Array) return StringRef(); auto *Left = dyn_cast(L); auto *Right = dyn_cast(R); assert(Left && Right); assert(Left->getChildrenCount() == 1); assert(Right->getChildrenCount() == 1); return getStringRepresentableChange(Left->getOnlyChild(), Right->getOnlyChild(), Raw); } bool detectArrayMemberChange(SDKNodeType* L, SDKNodeType *R) { // We only care if this the top-level type node. if (!L->isTopLevelType() || !R->isTopLevelType()) return false; StringRef Raw; StringRef KeyChangedTo; bool HasOptional = L->getTypeKind() == KnownTypeKind::Optional && R->getTypeKind() == KnownTypeKind::Optional; if (HasOptional) { // Detect [String]? to [StringRepresentableStruct]? Change KeyChangedTo = detectArrayMemberChangeInternal(L->getOnlyChild()->getAs(), R->getOnlyChild()->getAs(), Raw); } else { // Detect [String] to [StringRepresentableStruct] Change KeyChangedTo = detectArrayMemberChangeInternal(L, R, Raw); } if (!KeyChangedTo.empty()) { if (IsVisitingLeft) { L->annotate(HasOptional ? NodeAnnotation::OptionalArrayMemberUpdate : NodeAnnotation::ArrayMemberUpdate); L->annotate(NodeAnnotation::RawTypeLeft, Raw); L->annotate(NodeAnnotation::RawTypeRight, KeyChangedTo); } else { R->annotate(HasOptional ? NodeAnnotation::RevertOptionalArrayMemberUpdate : NodeAnnotation::RevertArrayMemberUpdate); R->annotate(NodeAnnotation::RawTypeLeft, KeyChangedTo); R->annotate(NodeAnnotation::RawTypeRight, Raw); } return true; } return false; } bool detectSimpleStringRepresentableUpdate(SDKNodeType *L, SDKNodeType *R) { if (!L->isTopLevelType() || !R->isTopLevelType()) return false; StringRef KeyChangedTo; StringRef Raw; bool HasOptional = L->getTypeKind() == KnownTypeKind::Optional && R->getTypeKind() == KnownTypeKind::Optional; if (HasOptional) { // Detect String? changes to StringRepresentableStruct? change. KeyChangedTo = getStringRepresentableChange(L->getOnlyChild()->getAs(), R->getOnlyChild()->getAs(), Raw); } else { // Detect String changes to StringRepresentableStruct change. KeyChangedTo = getStringRepresentableChange(L, R, Raw); } if (!KeyChangedTo.empty()) { if (IsVisitingLeft) { L->annotate(NodeAnnotation::RawTypeLeft, Raw); L->annotate(NodeAnnotation::RawTypeRight, KeyChangedTo); L->annotate(HasOptional ? NodeAnnotation::SimpleOptionalStringRepresentableUpdate: NodeAnnotation::SimpleStringRepresentableUpdate); } else { R->annotate(NodeAnnotation::RawTypeLeft, KeyChangedTo); R->annotate(NodeAnnotation::RawTypeRight, Raw); R->annotate(HasOptional ? NodeAnnotation::RevertSimpleOptionalStringRepresentableUpdate: NodeAnnotation::RevertSimpleStringRepresentableUpdate); } return true; } return false; } bool isUnhandledCase(SDKNodeType *Node, SDKNodeType *Counter) { return Node->getTypeKind() == KnownTypeKind::Void || Counter->getTypeKind() == KnownTypeKind::Void; } static void clearTypeRewritten(SDKNode *N) { if (!N->isAnnotatedAs(NodeAnnotation::TypeRewritten)) return; N->removeAnnotate(NodeAnnotation::TypeRewritten); N->removeAnnotate(NodeAnnotation::TypeRewrittenLeft); N->removeAnnotate(NodeAnnotation::TypeRewrittenRight); } public: InterfaceTypeChangeDetector(bool IsVisitingLeft): IsVisitingLeft(IsVisitingLeft) {} void detect(SDKNode *Left, SDKNode *Right) { auto *Node = dyn_cast(Left); auto *Counter = dyn_cast(Right); if (!Node || !Counter || isUnhandledCase(Node, Counter)) return; if (detectWrapOptional(Node, Counter) || detectOptionalUpdate(Node, Counter) || detectWrapImplicitOptional(Node, Counter) || detectUnmanagedUpdate(Node, Counter)) { // we may have detected type rewritten before (when visiting left), // so clear the annotation here. clearTypeRewritten(Node); clearTypeRewritten(Counter); } else { // Detect type re-written then. detectTypeRewritten(Node, Counter); } // The raw representable changes can co-exist with above attributes. auto Result = detectDictionaryKeyChange(Node, Counter) || detectArrayMemberChange(Node, Counter) || detectSimpleStringRepresentableUpdate(Node, Counter); (void) Result; return; } }; class ChangeRefinementPass : public SDKTreeDiffPass, public SDKNodeVisitor { UpdatedNodesMap &UpdateMap; InterfaceTypeChangeDetector LeftDetector; InterfaceTypeChangeDetector RightDetector; InterfaceTypeChangeDetector *Detector; public: ChangeRefinementPass(UpdatedNodesMap &UpdateMap) : UpdateMap(UpdateMap), LeftDetector(true), RightDetector(false), Detector(nullptr) {} void pass(NodePtr Left, NodePtr Right) override { // Post-order visit is necessary since we propagate annotations bottom-up Detector = &LeftDetector; SDKNode::postorderVisit(Left, *this); Detector = &RightDetector; SDKNode::postorderVisit(Right, *this); } void visit(NodePtr Node) override { assert(Detector); if (!Node || !Node->isAnnotatedAs(NodeAnnotation::Updated)) return; auto *Counter = UpdateMap.findUpdateCounterpart(Node); Detector->detect(Node, Counter); return; } }; } // end anonymous namespace static void findTypeMemberDiffs(NodePtr leftSDKRoot, NodePtr rightSDKRoot, TypeMemberDiffVector &out); static void printNode(llvm::raw_ostream &os, NodePtr node) { os << "{" << node->getName() << " " << node->getKind() << " " << node->getPrintedName(); if (auto F = dyn_cast(node)) { if (F->hasSelfIndex()) { os << " selfIndex: "; os << F->getSelfIndex(); } } os << "}"; } void TypeMemberDiffFinder::dump(llvm::raw_ostream &os) const { for (auto pair : getDiffs()) { os << " - "; printNode(os, pair.first); os << " parent: "; printNode(os, pair.first->getParent()); os << "\n + "; printNode(os, pair.second); os << " parent: "; printNode(os, pair.second->getParent()); os << "\n\n"; } } namespace { template void removeRedundantAndSort(std::vector &Diffs) { std::set DiffSet(Diffs.begin(), Diffs.end()); Diffs.assign(DiffSet.begin(), DiffSet.end()); std::sort(Diffs.begin(), Diffs.end()); } template void serializeDiffs(llvm::raw_ostream &Fs, std::vector &Diffs) { if (Diffs.empty()) return; Fs << "\n"; T::describe(Fs); for (auto &Diff : Diffs) { Diff.streamDef(Fs); Fs << "\n"; } T::undef(Fs); Fs << "\n"; } static bool isTypeChangeInterestedFuncNode(NodePtr Decl) { switch(Decl->getKind()) { case SDKNodeKind::DeclConstructor: case SDKNodeKind::DeclFunction: return true; default: return false; } } class DiffItemEmitter : public SDKNodeVisitor { DiffVector &AllItems; static bool isInterested(SDKNodeDecl* Decl, NodeAnnotation Anno) { switch (Anno) { case NodeAnnotation::WrapOptional: case NodeAnnotation::UnwrapOptional: case NodeAnnotation::ImplicitOptionalToOptional: case NodeAnnotation::OptionalToImplicitOptional: case NodeAnnotation::UnwrapUnmanaged: case NodeAnnotation::TypeRewritten: return isTypeChangeInterestedFuncNode(Decl) && Decl->getParent()->getKind() == SDKNodeKind::DeclType; default: return true; } } bool doesAncestorHaveTypeRewritten() { return std::find_if(Ancestors.begin(), Ancestors.end(),[](NodePtr N) { return N->isAnnotatedAs(NodeAnnotation::TypeRewritten); }) != Ancestors.end(); } static StringRef getLeftComment(NodePtr Node, NodeAnnotation Anno) { switch(Anno) { case NodeAnnotation::ArrayMemberUpdate: case NodeAnnotation::OptionalArrayMemberUpdate: case NodeAnnotation::DictionaryKeyUpdate: case NodeAnnotation::OptionalDictionaryKeyUpdate: case NodeAnnotation::SimpleStringRepresentableUpdate: case NodeAnnotation::SimpleOptionalStringRepresentableUpdate: case NodeAnnotation::RevertArrayMemberUpdate: case NodeAnnotation::RevertOptionalArrayMemberUpdate: case NodeAnnotation::RevertDictionaryKeyUpdate: case NodeAnnotation::RevertOptionalDictionaryKeyUpdate: case NodeAnnotation::RevertSimpleStringRepresentableUpdate: case NodeAnnotation::RevertSimpleOptionalStringRepresentableUpdate: return Node->getAnnotateComment(NodeAnnotation::RawTypeLeft); case NodeAnnotation::TypeRewritten: return Node->getAnnotateComment(NodeAnnotation::TypeRewrittenLeft); case NodeAnnotation::Rename: return Node->getAnnotateComment(NodeAnnotation::RenameOldName); default: return StringRef(); } } static StringRef getRightComment(NodePtr Node, NodeAnnotation Anno) { switch (Anno) { case NodeAnnotation::ArrayMemberUpdate: case NodeAnnotation::OptionalArrayMemberUpdate: case NodeAnnotation::DictionaryKeyUpdate: case NodeAnnotation::OptionalDictionaryKeyUpdate: case NodeAnnotation::SimpleStringRepresentableUpdate: case NodeAnnotation::SimpleOptionalStringRepresentableUpdate: case NodeAnnotation::RevertArrayMemberUpdate: case NodeAnnotation::RevertOptionalArrayMemberUpdate: case NodeAnnotation::RevertDictionaryKeyUpdate: case NodeAnnotation::RevertOptionalDictionaryKeyUpdate: case NodeAnnotation::RevertSimpleStringRepresentableUpdate: case NodeAnnotation::RevertSimpleOptionalStringRepresentableUpdate: return Node->getAnnotateComment(NodeAnnotation::RawTypeRight); case NodeAnnotation::TypeRewritten: return Node->getAnnotateComment(NodeAnnotation::TypeRewrittenRight); case NodeAnnotation::ModernizeEnum: return Node->getAnnotateComment(NodeAnnotation::ModernizeEnum); case NodeAnnotation::Rename: return Node->getAnnotateComment(NodeAnnotation::RenameNewName); case NodeAnnotation::GetterToProperty: case NodeAnnotation::SetterToProperty: return Node->getAnnotateComment(NodeAnnotation::PropertyName); default: return StringRef(); } } void handleAnnotations(NodePtr Node, SDKNodeDecl *NonTypeParent, StringRef Index, ArrayRef Annotations) { for (auto Anno: Annotations) { if (isInterested(NonTypeParent, Anno) && Node->isAnnotatedAs(Anno)) { auto Kind = NonTypeParent->getKind(); StringRef LC = getLeftComment(Node, Anno); StringRef RC = getRightComment(Node, Anno); AllItems.emplace_back(Kind, Anno, Index, NonTypeParent->getUsr(), StringRef(), LC, RC, NonTypeParent->getModuleName()); } } } void visit(NodePtr Node) override { auto *Parent = dyn_cast(Node); if (!Parent) { if (auto TN = dyn_cast(Node)) { Parent = TN->getClosestParentDecl(); } } if (!Parent) return; if (doesAncestorHaveTypeRewritten()) return; handleAnnotations(Node, Parent, isa(Node) ? getIndexString(Node) : "0", { #define NODE_ANNOTATION_CHANGE_KIND(NAME) NodeAnnotation::NAME, #include "swift/IDE/DigesterEnums.def" }); } StringRef getIndexString(NodePtr Node) { llvm::SmallString<32> Builder; std::vector Indexes; collectIndexes(Node, Indexes); auto First = true; for (auto I : Indexes) { if (!First) Builder.append(":"); else First = false; Builder.append(std::to_string(I)); } return Node->getSDKContext().buffer(Builder.str()); } void collectIndexes(NodePtr Node, std::vector &Indexes) { for (unsigned I = Ancestors.size(); I > 0 && (I == Ancestors.size() || isa(Ancestors[I])); -- I) { auto Child = I == Ancestors.size() ? Node : Ancestors[I]; auto Parent = Ancestors[I - 1]; Indexes.insert(Indexes.begin(), Parent->getChildIndex(Child)); } } DiffItemEmitter(DiffVector &AllItems) : AllItems(AllItems) {} public: static void collectDiffItems(NodePtr Root, DiffVector &DV) { DiffItemEmitter Emitter(DV); SDKNode::postorderVisit(Root, Emitter); } }; class DiagnosisEmitter : public SDKNodeVisitor { void handle(const SDKNodeDecl *D, NodeAnnotation Anno); void visitDecl(SDKNodeDecl *D); void visit(NodePtr Node) override; SDKNodeDecl *findAddedDecl(const SDKNodeDecl *Node); bool findTypeAliasDecl(const SDKNodeDecl *Node); static void collectAddedDecls(NodePtr Root, std::set &Results); std::set AddedDecls; UpdatedNodesMap &UpdateMap; NodeMap &TypeAliasUpdateMap; TypeMemberDiffVector &MemberChanges; DiagnosisEmitter(SDKContext &Ctx): UpdateMap(Ctx.getNodeUpdateMap()), TypeAliasUpdateMap(Ctx.getTypeAliasUpdateMap()), MemberChanges(Ctx.getTypeMemberDiffs()) {} public: static void diagnosis(NodePtr LeftRoot, NodePtr RightRoot, SDKContext &Ctx); }; void DiagnosisEmitter::collectAddedDecls(NodePtr Root, std::set &Results) { if (auto *D = dyn_cast(Root)) { if (Root->isAnnotatedAs(NodeAnnotation::Added)) Results.insert(D); } for (auto &C : Root->getChildren()) collectAddedDecls(C, Results); } SDKNodeDecl *DiagnosisEmitter::findAddedDecl(const SDKNodeDecl *Root) { for (auto *Added : AddedDecls) { if (Root->getKind() == Added->getKind() && Root->getPrintedName() == Added->getPrintedName() && Root->getUsr() == Added->getUsr()) return Added; } return nullptr; } bool DiagnosisEmitter::findTypeAliasDecl(const SDKNodeDecl *Node) { if (Node->getKind() != SDKNodeKind::DeclType) return false; return std::any_of(AddedDecls.begin(), AddedDecls.end(), [&](SDKNodeDecl *Added) { return Added->getKind() == SDKNodeKind::DeclTypeAlias && Added->getPrintedName() == Node->getPrintedName(); }); } void DiagnosisEmitter::diagnosis(NodePtr LeftRoot, NodePtr RightRoot, SDKContext &Ctx) { DiagnosisEmitter Emitter(Ctx); collectAddedDecls(RightRoot, Emitter.AddedDecls); SDKNode::postorderVisit(LeftRoot, Emitter); } static bool diagnoseRemovedExtensionMembers(const SDKNode *Node) { // If the removed decl is an extension, diagnose each member as being removed rather than // the extension itself has been removed. if (auto *DT= dyn_cast(Node)) { if (DT->isExtension()) { for (auto *C: DT->getChildren()) { auto *MD = cast(C); diagnoseRemovedDecl(MD); } return true; } } return false; } void DiagnosisEmitter::handle(const SDKNodeDecl *Node, NodeAnnotation Anno) { assert(Node->isAnnotatedAs(Anno)); auto &Ctx = Node->getSDKContext(); switch(Anno) { case NodeAnnotation::Removed: { // If we can find a type alias decl with the same name of this type, we // consider the type is not removed. if (findTypeAliasDecl(Node)) return; if (auto *Added = findAddedDecl(Node)) { if (Node->getDeclKind() != DeclKind::Constructor) { Node->emitDiag(Added->getLoc(), diag::moved_decl, Ctx.buffer((Twine(getDeclKindStr(Added->getDeclKind(), Ctx.getOpts().CompilerStyle)) + " " + Added->getFullyQualifiedName()).str())); return; } } // If we can find a hoisted member for this removed declaration, we // emit the diagnostics as rename instead of removal. auto It = std::find_if(MemberChanges.begin(), MemberChanges.end(), [&](TypeMemberDiffItem &Item) { return Item.usr == Node->getUsr(); }); if (It != MemberChanges.end()) { Node->emitDiag(SourceLoc(), diag::renamed_decl, Ctx.buffer((Twine(getDeclKindStr(Node->getDeclKind(), Ctx.getOpts().CompilerStyle)) + " " + It->newTypeName + "." + It->newPrintedName).str())); return; } // If a type alias of a raw type has been changed to a struct/enum that // conforms to RawRepresentable in the later version of SDK, we show the // refine diagnostics message instead of showing the type alias has been // removed. if (TypeAliasUpdateMap.find((SDKNode*)Node) != TypeAliasUpdateMap.end()) { Node->emitDiag(SourceLoc(), diag::raw_type_change, Node->getAs()->getUnderlyingType()->getPrintedName(), TypeAliasUpdateMap[(SDKNode*)Node]->getAs()-> getRawValueType()->getPrintedName()); return; } // We should exclude those declarations that are pulled up to the super classes. bool FoundInSuperclass = false; if (auto PD = dyn_cast(Node->getParent())) { if (PD->isAnnotatedAs(NodeAnnotation::Updated)) { // Get the updated counterpart of the parent decl. if (auto RTD = dyn_cast(UpdateMap. findUpdateCounterpart(PD))) { // Look up by the printed name in the counterpart. FoundInSuperclass = RTD->lookupChildByPrintedName(Node->getPrintedName()).has_value(); } } } if (FoundInSuperclass) return; // When diagnosing API changes, avoid complaining the removal of these // synthesized functions since they are compiler implementation details. // If an enum is no longer equatable, another diagnostic about removing // conforming protocol will be emitted. if (!Ctx.checkingABI()) { if (Node->getName() == Ctx.Id_derived_struct_equals || Node->getName() == Ctx.Id_derived_enum_equals) { return; } } bool handled = diagnoseRemovedExtensionMembers(Node); if (!handled) diagnoseRemovedDecl(Node); return; } case NodeAnnotation::Rename: { SourceLoc DiagLoc; // Try to get the source location from the later version of this node // via UpdateMap. if (auto CD = dyn_cast_or_null(UpdateMap .findUpdateCounterpart(Node))) { DiagLoc = CD->getLoc(); } if (isMissingDeclAcceptable(Node)) return; Node->emitDiag(DiagLoc, diag::renamed_decl, Ctx.buffer((Twine(getDeclKindStr(Node->getDeclKind(), Ctx.getOpts().CompilerStyle)) + " " + Node->getAnnotateComment(NodeAnnotation::RenameNewName)).str())); return; } default: return; } } void DiagnosisEmitter::visitDecl(SDKNodeDecl *Node) { std::vector Scratch; for (auto Anno : Node->getAnnotations(Scratch)) handle(Node, Anno); } void DiagnosisEmitter::visit(NodePtr Node) { if (auto *DNode = dyn_cast(Node)) { visitDecl(DNode); } } typedef std::vector NoEscapeFuncParamVector; class NoEscapingFuncEmitter : public SDKNodeVisitor { NoEscapeFuncParamVector &AllItems; NoEscapingFuncEmitter(NoEscapeFuncParamVector &AllItems) : AllItems(AllItems) {} void visit(NodePtr Node) override { if (Node->getKind() != SDKNodeKind::TypeFunc) return; if (Node->getAs()->isEscaping()) return; auto Parent = Node->getParent(); if (auto ParentFunc = dyn_cast(Parent)) { if (ParentFunc->isObjc()) { unsigned Index = ParentFunc->getChildIndex(Node); AllItems.emplace_back(ParentFunc->getUsr(), Index); } } } public: static void collectDiffItems(NodePtr Root, NoEscapeFuncParamVector &DV) { NoEscapingFuncEmitter Emitter(DV); SDKNode::postorderVisit(Root, Emitter); } }; } // end anonymous namespace namespace fs = llvm::sys::fs; namespace path = llvm::sys::path; class RenameDetectorForMemberDiff : public MatchedNodeListener { InterfaceTypeChangeDetector LeftDetector; InterfaceTypeChangeDetector RightDetector; public: RenameDetectorForMemberDiff(): LeftDetector(true), RightDetector(false) {} void foundMatch(NodePtr Left, NodePtr Right, NodeMatchReason Reason) override { if (!Left || !Right) return; swift::ide::api::detectRename(Left, Right); LeftDetector.detect(Left, Right); RightDetector.detect(Right, Left); } void workOn(NodePtr Left, NodePtr Right) { if (Left->getKind() == Right->getKind() && Left->getKind() == SDKNodeKind::DeclType) { SameNameNodeMatcher SNMatcher(Left->getChildren(), Right->getChildren(), *this); SNMatcher.match(); } if (Left->getKind() == Right->getKind() && Left->getKind() == SDKNodeKind::DeclVar) { SequentialNodeMatcher Matcher(Left->getChildren(), Right->getChildren(), *this); Matcher.match(); } } }; static std::optional findSelfIndex(SDKNode *Node) { if (auto func = dyn_cast(Node)) { return func->getSelfIndexOptional(); } else if (auto vd = dyn_cast(Node)) { for (auto &C : vd->getChildren()) { if (isa(C)) { if (auto Result = findSelfIndex(C)) return Result; } } } return std::nullopt; } /// Find cases where a diff is due to a change to being a type member static void findTypeMemberDiffs(NodePtr leftSDKRoot, NodePtr rightSDKRoot, TypeMemberDiffVector &out) { TypeMemberDiffFinder diffFinder(cast(leftSDKRoot)); diffFinder.findDiffsFor(rightSDKRoot); RenameDetectorForMemberDiff Detector; for (auto pair : diffFinder.getDiffs()) { auto left = pair.first; auto leftParent = left->getParent(); auto right = pair.second; auto rightParent = right->getParent(); // SDK_CHANGE_TYPE_MEMBER(USR, new type context name, new printed name, self // index, old printed name) TypeMemberDiffItem item = { right->getAs()->getUsr(), rightParent->getKind() == SDKNodeKind::Root ? StringRef() : rightParent->getAs()->getFullyQualifiedName(), right->getPrintedName(), findSelfIndex(right), std::nullopt, leftParent->getKind() == SDKNodeKind::Root ? StringRef() : leftParent->getAs()->getFullyQualifiedName(), left->getPrintedName()}; out.emplace_back(item); Detector.workOn(left, right); } } static std::vector> createDiagConsumer(llvm::raw_ostream &OS, bool &FailOnError, bool DisableFailOnError, bool CompilerStyleDiags, StringRef SerializedDiagPath) { std::vector> results; if (!SerializedDiagPath.empty()) { FailOnError = !DisableFailOnError; results.emplace_back(std::make_unique()); results.emplace_back(serialized_diagnostics::createConsumer(SerializedDiagPath, false)); } else if (CompilerStyleDiags) { FailOnError = !DisableFailOnError; results.emplace_back(std::make_unique()); } else { FailOnError = false; results.emplace_back(std::make_unique(true, OS)); } return results; } static int readFileLineByLine(StringRef Path, llvm::StringSet<> &Lines) { auto FileBufOrErr = llvm::MemoryBuffer::getFile(Path); if (!FileBufOrErr) { llvm::errs() << "error opening file '" << Path << "': " << FileBufOrErr.getError().message() << '\n'; return 1; } StringRef BufferText = FileBufOrErr.get()->getBuffer(); while (!BufferText.empty()) { StringRef Line; std::tie(Line, BufferText) = BufferText.split('\n'); Line = Line.trim(); if (Line.empty()) continue; if (Line.starts_with("// ")) // comment. continue; Lines.insert(Line); } return 0; } static bool readBreakageAllowlist(SDKContext &Ctx, llvm::StringSet<> &lines, StringRef BreakageAllowlistPath) { if (BreakageAllowlistPath.empty()) return 0; CompilerInstance instance; CompilerInvocation invoke; invoke.setModuleName("ForClangImporter"); std::string InstanceSetupError; if (instance.setup(invoke, InstanceSetupError)) { return 1; } auto importer = ClangImporter::create(instance.getASTContext()); SmallString<128> preprocessedFilePath; if (auto error = llvm::sys::fs::createTemporaryFile( "breakage-allowlist-", "txt", preprocessedFilePath)) { return 1; } if (importer->runPreprocessor(BreakageAllowlistPath, preprocessedFilePath.str())) { return 1; } return readFileLineByLine(preprocessedFilePath, lines); } static int diagnoseModuleChange(SDKContext &Ctx, SDKNodeRoot *LeftModule, SDKNodeRoot *RightModule, StringRef OutputPath, llvm::StringSet<> ProtocolReqAllowlist, bool DisableFailOnError, bool CompilerStyleDiags, bool ExplicitErrOnABIBreakage, StringRef SerializedDiagPath, StringRef BreakageAllowlistPath, bool DebugMapping) { assert(LeftModule); assert(RightModule); llvm::raw_ostream *OS = &llvm::errs(); if (!LeftModule || !RightModule) { *OS << "Cannot diagnose null SDKNodeRoot"; exit(1); } std::unique_ptr FileOS; if (!OutputPath.empty()) { std::error_code EC; FileOS.reset(new llvm::raw_fd_ostream(OutputPath, EC, llvm::sys::fs::OF_None)); OS = FileOS.get(); } bool FailOnError; auto allowedBreakages = std::make_unique>(); if (readBreakageAllowlist(Ctx, *allowedBreakages, BreakageAllowlistPath)) { Ctx.getDiags().diagnose(SourceLoc(), diag::cannot_read_allowlist, BreakageAllowlistPath); } auto shouldDowngrade = false; // If explicitly specified, avoid downgrading ABI breakage errors to warnings. if (ExplicitErrOnABIBreakage) { shouldDowngrade = false; } auto pConsumer = std::make_unique( createDiagConsumer(*OS, FailOnError, DisableFailOnError, CompilerStyleDiags, SerializedDiagPath), std::move(allowedBreakages), /*DowngradeToWarning*/shouldDowngrade); SWIFT_DEFER { pConsumer->finishProcessing(); }; Ctx.addDiagConsumer(*pConsumer); Ctx.setCommonVersion(std::min(LeftModule->getJsonFormatVersion(), RightModule->getJsonFormatVersion())); TypeAliasDiffFinder(LeftModule, RightModule, Ctx.getTypeAliasUpdateMap()).search(); PrunePass Prune(Ctx, std::move(ProtocolReqAllowlist), DebugMapping); Prune.pass(LeftModule, RightModule); ChangeRefinementPass RefinementPass(Ctx.getNodeUpdateMap()); RefinementPass.pass(LeftModule, RightModule); // Find member hoist changes to help refine diagnostics. findTypeMemberDiffs(LeftModule, RightModule, Ctx.getTypeMemberDiffs()); DiagnosisEmitter::diagnosis(LeftModule, RightModule, Ctx); return FailOnError && pConsumer->hasError() ? 1 : 0; } static int diagnoseModuleChange(StringRef LeftPath, StringRef RightPath, StringRef OutputPath, CheckerOptions Opts, llvm::StringSet<> ProtocolReqAllowlist, bool DisableFailOnError, bool CompilerStyleDiags, bool ExplicitErrOnABIBreakage, StringRef SerializedDiagPath, StringRef BreakageAllowlistPath, bool DebugMapping) { if (!fs::exists(LeftPath)) { llvm::errs() << LeftPath << " does not exist\n"; return 1; } if (!fs::exists(RightPath)) { llvm::errs() << RightPath << " does not exist\n"; return 1; } SDKContext Ctx(Opts); SwiftDeclCollector LeftCollector(Ctx); LeftCollector.deSerialize(LeftPath); SwiftDeclCollector RightCollector(Ctx); RightCollector.deSerialize(RightPath); return diagnoseModuleChange( Ctx, LeftCollector.getSDKRoot(), RightCollector.getSDKRoot(), OutputPath, std::move(ProtocolReqAllowlist), DisableFailOnError, ExplicitErrOnABIBreakage, CompilerStyleDiags, SerializedDiagPath, BreakageAllowlistPath, DebugMapping); } static void populateAliasChanges(NodeMap &AliasMap, DiffVector &AllItems, const bool isRevert) { for (auto Pair: AliasMap) { auto UnderlyingType = Pair.first->getAs()-> getUnderlyingType()->getPrintedName(); auto RawType = AliasMap[(SDKNode*)Pair.first]->getAs()-> getRawValueType()->getPrintedName(); if (isRevert) { auto *D = Pair.second->getAs(); AllItems.emplace_back(SDKNodeKind::DeclType, NodeAnnotation::RevertTypeAliasDeclToRawRepresentable, "0", D->getUsr(), "", RawType, UnderlyingType, D->getModuleName()); } else { auto *D = Pair.first->getAs(); AllItems.emplace_back(SDKNodeKind::DeclTypeAlias, NodeAnnotation::TypeAliasDeclToRawRepresentable, "0", D->getUsr(), "", UnderlyingType, RawType, D->getModuleName()); } } } static int generateMigrationScript(StringRef LeftPath, StringRef RightPath, StringRef DiffPath, llvm::StringSet<> &IgnoredRemoveUsrs, CheckerOptions Opts, bool OutputInJson, bool DebugMapping) { if (!fs::exists(LeftPath)) { llvm::errs() << LeftPath << " does not exist\n"; return 1; } if (!fs::exists(RightPath)) { llvm::errs() << RightPath << " does not exist\n"; return 1; } llvm::errs() << "Diffing: " << LeftPath << " and " << RightPath << "\n"; std::unique_ptr pConsumer = Opts.CompilerStyle ? std::make_unique() : std::make_unique(false); SDKContext Ctx(Opts); Ctx.addDiagConsumer(*pConsumer); SwiftDeclCollector LeftCollector(Ctx); LeftCollector.deSerialize(LeftPath); SwiftDeclCollector RightCollector(Ctx); RightCollector.deSerialize(RightPath); llvm::errs() << "Finished deserializing" << "\n"; auto LeftModule = LeftCollector.getSDKRoot(); auto RightModule = RightCollector.getSDKRoot(); Ctx.setCommonVersion(std::min(LeftModule->getJsonFormatVersion(), RightModule->getJsonFormatVersion())); // Structural diffs: not merely name changes but changes in SDK tree // structure. llvm::errs() << "Detecting type member diffs" << "\n"; findTypeMemberDiffs(LeftModule, RightModule, Ctx.getTypeMemberDiffs()); PrunePass Prune(Ctx, DebugMapping); Prune.pass(LeftModule, RightModule); llvm::errs() << "Finished pruning" << "\n"; ChangeRefinementPass RefinementPass(Ctx.getNodeUpdateMap()); RefinementPass.pass(LeftModule, RightModule); DiffVector AllItems; DiffItemEmitter::collectDiffItems(LeftModule, AllItems); // Find type alias change first. auto &AliasMap = Ctx.getTypeAliasUpdateMap(); TypeAliasDiffFinder(LeftModule, RightModule, AliasMap).search(); populateAliasChanges(AliasMap, AllItems, /*IsRevert*/false); // Find type alias revert change. auto &RevertAliasMap = Ctx.getRevertTypeAliasUpdateMap(); TypeAliasDiffFinder(RightModule, LeftModule, RevertAliasMap).search(); populateAliasChanges(RevertAliasMap, AllItems, /*IsRevert*/true); AllItems.erase( std::remove_if(AllItems.begin(), AllItems.end(), [&](CommonDiffItem &Item) { return Item.DiffKind == NodeAnnotation::RemovedDecl && IgnoredRemoveUsrs.contains(Item.LeftUsr); }), AllItems.end()); NoEscapeFuncParamVector AllNoEscapingFuncs; NoEscapingFuncEmitter::collectDiffItems(RightModule, AllNoEscapingFuncs); llvm::errs() << "Dumping diff to " << DiffPath << '\n'; std::vector Overloads; // OverloadMemberFunctionEmitter::collectDiffItems(RightModule, Overloads); auto &typeMemberDiffs = Ctx.getTypeMemberDiffs(); std::error_code EC; llvm::raw_fd_ostream Fs(DiffPath, EC, llvm::sys::fs::OF_None); removeRedundantAndSort(AllItems); removeRedundantAndSort(typeMemberDiffs); removeRedundantAndSort(AllNoEscapingFuncs); removeRedundantAndSort(Overloads); if (OutputInJson) { std::vector TotalItems; llvm::transform(AllItems, std::back_inserter(TotalItems), [](CommonDiffItem &Item) { return &Item; }); llvm::transform(typeMemberDiffs, std::back_inserter(TotalItems), [](TypeMemberDiffItem &Item) { return &Item; }); llvm::transform(AllNoEscapingFuncs, std::back_inserter(TotalItems), [](NoEscapeFuncParam &Item) { return &Item; }); llvm::transform(Overloads, std::back_inserter(TotalItems), [](OverloadedFuncInfo &Item) { return &Item; }); APIDiffItemStore::serialize(Fs, TotalItems); return 0; } serializeDiffs(Fs, AllItems); serializeDiffs(Fs, typeMemberDiffs); serializeDiffs(Fs, AllNoEscapingFuncs); serializeDiffs(Fs, Overloads); return 0; } static void setSDKPath(CompilerInvocation &InitInvoke, bool IsBaseline, StringRef SDK, StringRef BaselineSDK) { if (IsBaseline) { // Set baseline SDK if (!BaselineSDK.empty()) { InitInvoke.setSDKPath(BaselineSDK.str()); } } else { // Set current SDK if (!SDK.empty()) { InitInvoke.setSDKPath(SDK.str()); } else if (const char *SDKROOT = getenv("SDKROOT")) { InitInvoke.setSDKPath(SDKROOT); } } } static int deserializeDiffItems(APIDiffItemStore &Store, StringRef DiffPath, StringRef OutputPath) { Store.addStorePath(DiffPath); std::error_code EC; llvm::raw_fd_ostream FS(OutputPath, EC, llvm::sys::fs::OF_None); APIDiffItemStore::serialize(FS, Store.getAllDiffItems()); return 0; } static int deserializeNameCorrection(APIDiffItemStore &Store, StringRef OutputPath) { std::error_code EC; llvm::raw_fd_ostream FS(OutputPath, EC, llvm::sys::fs::OF_None); std::set Result; for (auto *Item: Store.getAllDiffItems()) { if (auto *CI = dyn_cast(Item)) { if (CI->DiffKind == NodeAnnotation::Rename) { auto NewName = CI->getNewName(); auto Module = CI->ModuleName; if (CI->rightCommentUnderscored()) { Result.insert(NameCorrectionInfo(NewName, NewName, Module)); } } } } std::vector Vec; Vec.insert(Vec.end(), Result.begin(), Result.end()); APIDiffItemStore::serialize(FS, Vec); return EC.value(); } static std::string getDefaultBaselineDir(std::string MainExecutablePath) { llvm::SmallString<128> BaselineDir; BaselineDir.append(MainExecutablePath); llvm::sys::path::remove_filename(BaselineDir); // Remove /swift-api-digester llvm::sys::path::remove_filename(BaselineDir); // Remove /bin llvm::sys::path::append(BaselineDir, "lib", "swift", "FrameworkABIBaseline"); return BaselineDir.str().str(); } static std::string getEmptyBaselinePath(std::string MainExecutablePath) { llvm::SmallString<128> BaselinePath( getDefaultBaselineDir(MainExecutablePath)); llvm::sys::path::append(BaselinePath, "nil.json"); return BaselinePath.str().str(); } static StringRef getBaselineFilename(llvm::Triple Triple) { if (Triple.isMacCatalystEnvironment()) return "iosmac.json"; else if (Triple.isMacOSX()) return "macos.json"; else if (Triple.isiOS()) return "iphoneos.json"; else if (Triple.isTvOS()) return "appletvos.json"; else if (Triple.isWatchOS()) return "watchos.json"; else if (Triple.isOSLinux()) return "linux.json"; else if (Triple.isOSWindows()) return "windows.json"; else if (Triple.isXROS()) return "xros.json"; else { llvm::errs() << "Unsupported triple target\n"; exit(1); } } static std::string getDefaultBaselinePath(std::string MainExecutablePath, StringRef Module, llvm::Triple Triple, bool ABI) { llvm::SmallString<128> BaselinePath( getDefaultBaselineDir(MainExecutablePath)); llvm::sys::path::append(BaselinePath, Module); // Look for ABI or API baseline llvm::sys::path::append(BaselinePath, ABI? "ABI": "API"); llvm::sys::path::append(BaselinePath, getBaselineFilename(Triple)); return BaselinePath.str().str(); } static std::string getCustomBaselinePath(llvm::Triple Triple, bool ABI, StringRef BaselineDirPath) { llvm::SmallString<128> BaselinePath(BaselineDirPath); // Look for ABI or API baseline llvm::sys::path::append(BaselinePath, ABI? "ABI": "API"); llvm::sys::path::append(BaselinePath, getBaselineFilename(Triple)); return BaselinePath.str().str(); } static std::string getJsonOutputFilePath(llvm::Triple Triple, bool ABI, std::string &OutputFile, std::string &OutputDir) { if (!OutputFile.empty()) return OutputFile; if (!OutputDir.empty()) { llvm::SmallString<128> OutputPath(OutputDir); llvm::sys::path::append(OutputPath, ABI? "ABI": "API"); if (!llvm::sys::fs::exists(OutputPath.str())) { llvm::errs() << "Baseline directory " << OutputPath.str() << " doesn't exist\n"; exit(1); } llvm::sys::path::append(OutputPath, getBaselineFilename(Triple)); return OutputPath.str().str(); } llvm::errs() << "Unable to decide output file path\n"; exit(1); } class SwiftAPIDigesterInvocation { private: std::string MainExecutablePath; std::unique_ptr Table; CompilerInvocation InitInvoke; ActionType Action = ActionType::None; CheckerOptions CheckerOpts; llvm::StringSet<> IgnoredUsrs; std::string ProtReqAllowList; std::vector SDKJsonPaths; std::string OutputFile; std::string OutputDir; bool CompilerStyleDiags; bool ExplicitErrOnABIBreakage; std::string SerializedDiagPath; std::string BaselineFilePath; std::string BaselineDirPath; bool UseEmptyBaseline; bool Verbose; bool DebugMapping; std::string BreakageAllowlistPath; bool OutputInJson; std::string SDK; std::string BaselineSDK; std::string Triple; std::string SwiftVersion; std::vector SystemFrameworkPaths; std::vector BaselineFrameworkPaths; std::vector FrameworkPaths; std::vector SystemModuleImportPaths; std::vector BaselineModuleImportPaths; std::vector ModuleImportPaths; std::string ModuleList; std::vector ModuleNames; std::vector PreferInterfaceForModules; std::string ResourceDir; std::string ModuleCachePath; bool DisableFailOnError; public: SwiftAPIDigesterInvocation(const std::string &ExecPath) : MainExecutablePath(ExecPath), Table(createSwiftOptTable()) {} int parseArgs(ArrayRef Args) { unsigned MissingIndex; unsigned MissingCount; llvm::opt::InputArgList ParsedArgs = Table->ParseArgs( Args, MissingIndex, MissingCount, SwiftAPIDigesterOption); if (MissingCount) { llvm::errs() << "error: missing argument for option '" << ParsedArgs.getArgString(MissingIndex) << "'\n"; return 1; } if (ParsedArgs.hasArg(OPT_UNKNOWN)) { for (const auto *A : ParsedArgs.filtered(OPT_UNKNOWN)) { llvm::errs() << "error: unknown argument '" << A->getAsString(ParsedArgs) << "'\n"; } return 1; } if (ParsedArgs.getLastArg(OPT_help)) { printHelp(); return 1; } if (auto *A = ParsedArgs.getLastArg( OPT_dump_sdk, OPT_generate_migration_script, OPT_diagnose_sdk, OPT_deserialize_diff, OPT_deserialize_sdk, OPT_find_usr, OPT_generate_name_correction, OPT_generate_empty_baseline)) { switch (A->getOption().getID()) { case OPT_dump_sdk: Action = ActionType::DumpSDK; break; case OPT_generate_migration_script: Action = ActionType::MigratorGen; break; case OPT_diagnose_sdk: Action = ActionType::DiagnoseSDKs; break; case OPT_deserialize_diff: Action = ActionType::DeserializeDiffItems; break; case OPT_deserialize_sdk: Action = ActionType::DeserializeSDK; break; case OPT_find_usr: Action = ActionType::FindUsr; break; case OPT_generate_name_correction: Action = ActionType::GenerateNameCorrectionTemplate; break; case OPT_generate_empty_baseline: Action = ActionType::GenerateEmptyBaseline; break; } } if (auto *A = ParsedArgs.getLastArg(OPT_ignored_usrs)) { auto Path = A->getValue(); if (!fs::exists(Path)) { llvm::errs() << Path << " does not exist.\n"; return 1; } readFileLineByLine(Path, IgnoredUsrs); } ProtReqAllowList = ParsedArgs.getLastArgValue(OPT_protocol_requirement_allow_list).str(); SDKJsonPaths = ParsedArgs.getAllArgValues(OPT_input_paths); OutputFile = ParsedArgs.getLastArgValue(OPT_o).str(); OutputDir = ParsedArgs.getLastArgValue(OPT_output_dir).str(); CompilerStyleDiags = ParsedArgs.hasArg(OPT_compiler_style_diags); ExplicitErrOnABIBreakage = ParsedArgs.hasArg(OPT_error_on_abi_breakage); SerializedDiagPath = ParsedArgs.getLastArgValue(OPT_serialize_diagnostics_path).str(); BaselineFilePath = ParsedArgs.getLastArgValue(OPT_baseline_path).str(); BaselineDirPath = ParsedArgs.getLastArgValue(OPT_baseline_dir).str(); UseEmptyBaseline = ParsedArgs.hasArg(OPT_empty_baseline); Verbose = ParsedArgs.hasArg(OPT_v); BreakageAllowlistPath = ParsedArgs.getLastArgValue(OPT_breakage_allowlist_path).str(); OutputInJson = ParsedArgs.hasArg(OPT_json); SDK = ParsedArgs.getLastArgValue(OPT_sdk).str(); BaselineSDK = ParsedArgs.getLastArgValue(OPT_bsdk).str(); Triple = ParsedArgs.getLastArgValue(OPT_target).str(); SwiftVersion = ParsedArgs.getLastArgValue(OPT_swift_version).str(); SystemFrameworkPaths = ParsedArgs.getAllArgValues(OPT_Fsystem); llvm::append_range(SystemFrameworkPaths, ParsedArgs.getAllArgValues(OPT_iframework)); BaselineFrameworkPaths = ParsedArgs.getAllArgValues(OPT_BF); FrameworkPaths = ParsedArgs.getAllArgValues(OPT_F); SystemModuleImportPaths = ParsedArgs.getAllArgValues(OPT_Isystem); BaselineModuleImportPaths = ParsedArgs.getAllArgValues(OPT_BI); ModuleImportPaths = ParsedArgs.getAllArgValues(OPT_I); ModuleList = ParsedArgs.getLastArgValue(OPT_module_list_file).str(); ModuleNames = ParsedArgs.getAllArgValues(OPT_module); PreferInterfaceForModules = ParsedArgs.getAllArgValues(OPT_use_interface_for_module); ResourceDir = ParsedArgs.getLastArgValue(OPT_resource_dir).str(); ModuleCachePath = ParsedArgs.getLastArgValue(OPT_module_cache_path).str(); DebugMapping = ParsedArgs.hasArg(OPT_debug_mapping); DisableFailOnError = ParsedArgs.hasArg(OPT_disable_fail_on_error); CheckerOpts.AvoidLocation = ParsedArgs.hasArg(OPT_avoid_location); CheckerOpts.AvoidToolArgs = ParsedArgs.hasArg(OPT_avoid_tool_args); CheckerOpts.ABI = ParsedArgs.hasArg(OPT_abi); CheckerOpts.Migrator = ParsedArgs.hasArg(OPT_migrator); CheckerOpts.Verbose = Verbose; CheckerOpts.AbortOnModuleLoadFailure = ParsedArgs.hasArg(OPT_abort_on_module_fail); CheckerOpts.LocationFilter = ParsedArgs.getLastArgValue(OPT_location); CheckerOpts.PrintModule = ParsedArgs.hasArg(OPT_print_module); // When ABI checking is enabled, we should only include Swift symbols // because the checking logics are language-specific. CheckerOpts.SwiftOnly = ParsedArgs.hasArg(OPT_abi) || ParsedArgs.hasArg(OPT_swift_only); CheckerOpts.SkipOSCheck = ParsedArgs.hasArg(OPT_disable_os_checks); CheckerOpts.SkipRemoveDeprecatedCheck = ParsedArgs.hasArg(OPT_disable_remove_deprecated_check); if (ParsedArgs.hasArg(OPT_enable_remove_deprecated_check)) { CheckerOpts.SkipRemoveDeprecatedCheck = false; } CheckerOpts.CompilerStyle = CompilerStyleDiags || !SerializedDiagPath.empty(); for (auto Arg : Args) CheckerOpts.ToolArgs.push_back(Arg); for(auto spi: ParsedArgs.getAllArgValues(OPT_ignore_spi_groups)) CheckerOpts.SPIGroupNamesToIgnore.insert(spi); if (!SDK.empty()) { auto Ver = getSDKBuildVersion(SDK); if (!Ver.empty()) { CheckerOpts.ToolArgs.push_back("-sdk-version"); CheckerOpts.ToolArgs.push_back(Ver); } } return 0; } void printHelp() { std::string ExecutableName = llvm::sys::path::stem(MainExecutablePath).str(); Table->printHelp(llvm::outs(), ExecutableName.c_str(), "Swift API Digester", /*IncludedFlagsBitmask*/ SwiftAPIDigesterOption, /*ExcludedFlagsBitmask*/ 0, /*ShowAllAliases*/ false); } bool hasBaselineInput() { return !BaselineModuleImportPaths.empty() || !BaselineFrameworkPaths.empty() || !BaselineSDK.empty(); } enum class ComparisonInputMode : uint8_t { BothJson, BaselineJson, BothLoad, }; ComparisonInputMode checkComparisonInputMode() { if (SDKJsonPaths.size() == 2) return ComparisonInputMode::BothJson; else if (hasBaselineInput()) return ComparisonInputMode::BothLoad; else return ComparisonInputMode::BaselineJson; } int prepareForDump(CompilerInvocation &InitInvoke, llvm::StringSet<> &Modules, bool IsBaseline = false) { InitInvoke.setMainExecutablePath(MainExecutablePath); InitInvoke.setModuleName("swift_ide_test"); setSDKPath(InitInvoke, IsBaseline, SDK, BaselineSDK); if (!Triple.empty()) InitInvoke.setTargetTriple(Triple); // Ensure the tool works on linux properly InitInvoke.getLangOptions().EnableObjCInterop = InitInvoke.getLangOptions().Target.isOSDarwin(); InitInvoke.getClangImporterOptions().ModuleCachePath = ModuleCachePath; // Module recovery issue shouldn't bring down the tool. InitInvoke.getLangOptions().AllowDeserializingImplementationOnly = true; if (!SwiftVersion.empty()) { using version::Version; bool isValid = false; if (auto Version = VersionParser::parseVersionString( SwiftVersion, SourceLoc(), nullptr)) { if (auto Effective = Version.value().getEffectiveLanguageVersion()) { InitInvoke.getLangOptions().EffectiveLanguageVersion = *Effective; isValid = true; } } if (!isValid) { llvm::errs() << "Unsupported Swift Version.\n"; exit(1); } } if (!ResourceDir.empty()) { InitInvoke.setRuntimeResourcePath(ResourceDir); } std::vector FramePaths; for (const auto &path : SystemFrameworkPaths) { FramePaths.push_back({path, /*isSystem=*/true}); } std::vector ImportPaths; for (const auto &path : SystemModuleImportPaths) { ImportPaths.push_back({path, /*isSystem=*/true}); } if (IsBaseline) { for (const auto &path : BaselineFrameworkPaths) { FramePaths.push_back({path, /*isSystem=*/false}); } for (const auto &path : BaselineModuleImportPaths) { ImportPaths.push_back({path, /*isSystem=*/false}); } } else { for (const auto &path : FrameworkPaths) { FramePaths.push_back({path, /*isSystem=*/false}); } for (const auto &path : ModuleImportPaths) { ImportPaths.push_back({path, /*isSystem=*/false}); } } InitInvoke.setFrameworkSearchPaths(FramePaths); InitInvoke.setImportSearchPaths(ImportPaths); if (!ModuleList.empty()) { if (readFileLineByLine(ModuleList, Modules)) exit(1); } for (auto M : ModuleNames) { Modules.insert(M); } for (auto M : PreferInterfaceForModules) { InitInvoke.getFrontendOptions().PreferInterfaceForModules.push_back(M); } if (Modules.empty()) { llvm::errs() << "Need to specify -include-all or -module \n"; exit(1); } return 0; } SDKNodeRoot *getSDKRoot(SDKContext &Ctx, bool IsBaseline) { CompilerInvocation Invoke; llvm::StringSet<> Modules; if (prepareForDump(Invoke, Modules, IsBaseline)) return nullptr; return getSDKNodeRoot(Ctx, Invoke, Modules); } SDKNodeRoot *getBaselineFromJson(SDKContext &Ctx) { SwiftDeclCollector Collector(Ctx); CompilerInvocation Invoke; llvm::StringSet<> Modules; // We need to call prepareForDump to parse target triple. if (prepareForDump(Invoke, Modules, true)) return nullptr; assert(Modules.size() == 1 && "Cannot find builtin baseline for more than one module"); std::string Path; // If the baseline path has been given, honor that. if (!BaselineFilePath.empty()) { Path = BaselineFilePath; } else if (!BaselineDirPath.empty()) { Path = getCustomBaselinePath(Invoke.getLangOptions().Target, Ctx.checkingABI(), BaselineDirPath); } else if (UseEmptyBaseline) { Path = getEmptyBaselinePath(MainExecutablePath); } else { Path = getDefaultBaselinePath( MainExecutablePath, Modules.begin()->getKey(), Invoke.getLangOptions().Target, Ctx.checkingABI()); } if (!fs::exists(Path)) { llvm::errs() << "Baseline at " << Path << " does not exist\n"; exit(1); } if (Verbose) { llvm::errs() << "Using baseline at " << Path << "\n"; } Collector.deSerialize(Path); return Collector.getSDKRoot(); } int run(ArrayRef Args) { switch (Action) { case ActionType::DumpSDK: { llvm::StringSet<> Modules; if (prepareForDump(InitInvoke, Modules)) return 1; auto JsonOut = getJsonOutputFilePath(InitInvoke.getLangOptions().Target, CheckerOpts.ABI, OutputFile, OutputDir); std::error_code EC; llvm::raw_fd_ostream fs(JsonOut, EC); if (EC) { llvm::errs() << "Cannot open JSON output file: " << JsonOut << "\n"; return 1; } return dumpSDKContent(InitInvoke, Modules, fs, CheckerOpts); } case ActionType::MigratorGen: case ActionType::DiagnoseSDKs: { ComparisonInputMode Mode = checkComparisonInputMode(); llvm::StringSet<> protocolAllowlist; if (!ProtReqAllowList.empty()) { if (readFileLineByLine(ProtReqAllowList, protocolAllowlist)) return 1; } if (Action == ActionType::MigratorGen) { assert(Mode == ComparisonInputMode::BothJson && "Only BothJson mode is supported"); return generateMigrationScript(SDKJsonPaths[0], SDKJsonPaths[1], OutputFile, IgnoredUsrs, CheckerOpts, OutputInJson, DebugMapping); } switch (Mode) { case ComparisonInputMode::BothJson: { return diagnoseModuleChange( SDKJsonPaths[0], SDKJsonPaths[1], OutputFile, CheckerOpts, std::move(protocolAllowlist), DisableFailOnError, CompilerStyleDiags, ExplicitErrOnABIBreakage, SerializedDiagPath, BreakageAllowlistPath, DebugMapping); } case ComparisonInputMode::BaselineJson: { SDKContext Ctx(CheckerOpts); return diagnoseModuleChange( Ctx, getBaselineFromJson(Ctx), getSDKRoot(Ctx, false), OutputFile, std::move(protocolAllowlist), DisableFailOnError, CompilerStyleDiags, ExplicitErrOnABIBreakage, SerializedDiagPath, BreakageAllowlistPath, DebugMapping); } case ComparisonInputMode::BothLoad: { SDKContext Ctx(CheckerOpts); return diagnoseModuleChange( Ctx, getSDKRoot(Ctx, true), getSDKRoot(Ctx, false), OutputFile, std::move(protocolAllowlist), DisableFailOnError, CompilerStyleDiags, ExplicitErrOnABIBreakage, SerializedDiagPath, BreakageAllowlistPath, DebugMapping); } } } case ActionType::DeserializeSDK: case ActionType::DeserializeDiffItems: { if (SDKJsonPaths.size() != 1) { printHelp(); return 1; } if (Action == ActionType::DeserializeDiffItems) { CompilerInstance CI; APIDiffItemStore Store(CI.getDiags()); return deserializeDiffItems(Store, SDKJsonPaths[0], OutputFile); } else { return deserializeSDKDump(SDKJsonPaths[0], OutputFile, CheckerOpts); } } case ActionType::GenerateNameCorrectionTemplate: { CompilerInstance CI; APIDiffItemStore Store(CI.getDiags()); auto &Paths = SDKJsonPaths; for (unsigned I = 0; I < Paths.size(); I++) Store.addStorePath(Paths[I]); return deserializeNameCorrection(Store, OutputFile); } case ActionType::GenerateEmptyBaseline: { SDKContext Ctx(CheckerOpts); std::error_code EC; llvm::raw_fd_ostream fs(OutputFile, EC); if (EC) { llvm::errs() << "Cannot open output file: " << OutputFile << "\n"; return 1; } dumpSDKRoot(getEmptySDKNodeRoot(Ctx), fs); return 0; } case ActionType::FindUsr: { if (SDKJsonPaths.size() != 1) { printHelp(); return 1; } return findDeclUsr(SDKJsonPaths[0], CheckerOpts); } case ActionType::None: llvm::errs() << "Action required\n"; printHelp(); return 1; } } }; int swift_api_digester_main(ArrayRef Args, const char *Argv0, void *MainAddr) { INITIALIZE_LLVM(); std::string MainExecutablePath = fs::getMainExecutable(Argv0, MainAddr); SwiftAPIDigesterInvocation Invocation(MainExecutablePath); if (Invocation.parseArgs(Args) != 0) return EXIT_FAILURE; if (Invocation.run(Args) != 0) return EXIT_FAILURE; return EXIT_SUCCESS; }