blob: a415bba642691efb51fc4acfea0d5cef90a2aa16 [file] [log] [blame]
//===--- 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/AST/DiagnosticsModuleDiffer.h"
#include "swift/IDE/APIDigesterData.h"
#include <functional>
#include "ModuleAnalyzerNodes.h"
#include "ModuleDiagsConsumer.h"
using namespace swift;
using namespace ide;
using namespace api;
namespace {
enum class ActionType {
None,
DumpSDK,
DumpSwiftModules,
CompareSDKs,
DiagnoseSDKs,
// The following two are for testing purposes
DeserializeDiffItems,
DeserializeSDK,
GenerateNameCorrectionTemplate,
FindUsr,
};
} // end anonymous namespace
namespace options {
static llvm::cl::opt<bool>
IncludeAllModules("include-all", llvm::cl::desc("Include all modules from the SDK"));
static llvm::cl::list<std::string>
ModuleNames("module", llvm::cl::ZeroOrMore, llvm::cl::desc("Names of modules"));
static llvm::cl::opt<std::string>
ModuleList("module-list-file",
llvm::cl::desc("File containing a new-line separated list of modules"));
static llvm::cl::opt<std::string>
ProtReqWhiteList("protocol-requirement-white-list",
llvm::cl::desc("File containing a new-line separated list of protocol names"));
static llvm::cl::opt<std::string>
OutputFile("o", llvm::cl::desc("Output file"));
static llvm::cl::opt<std::string>
SDK("sdk", llvm::cl::desc("path to the SDK to build against"));
static llvm::cl::opt<std::string>
Triple("target", llvm::cl::desc("target triple"));
static llvm::cl::opt<std::string>
ModuleCachePath("module-cache-path", llvm::cl::desc("Clang module cache path"));
static llvm::cl::opt<std::string>
ResourceDir("resource-dir",
llvm::cl::desc("The directory that holds the compiler resource files"));
static llvm::cl::list<std::string>
FrameworkPaths("F", llvm::cl::desc("add a directory to the framework search path"));
static llvm::cl::list<std::string>
ModuleInputPaths("I", llvm::cl::desc("add a module for input"));
static llvm::cl::list<std::string>
CCSystemFrameworkPaths("iframework",
llvm::cl::desc("add a directory to the clang importer system framework search path"));
static llvm::cl::opt<bool>
AbortOnModuleLoadFailure("abort-on-module-fail",
llvm::cl::desc("Abort if a module failed to load"));
static llvm::cl::opt<bool>
Verbose("v", llvm::cl::desc("Verbose"));
static llvm::cl::opt<bool>
Abi("abi", llvm::cl::desc("Dumping ABI interface"), llvm::cl::init(false));
static llvm::cl::opt<bool>
SwiftOnly("swift-only",
llvm::cl::desc("Only include APIs defined from Swift source"),
llvm::cl::init(false));
static llvm::cl::opt<bool>
PrintModule("print-module", llvm::cl::desc("Print module names in diagnostics"));
static llvm::cl::opt<ActionType>
Action(llvm::cl::desc("Mode:"), llvm::cl::init(ActionType::None),
llvm::cl::values(
clEnumValN(ActionType::DumpSDK,
"dump-sdk",
"Dump SDK content to JSON file"),
clEnumValN(ActionType::DumpSwiftModules,
"dump-swift",
"dump swift modules in SDK"),
clEnumValN(ActionType::CompareSDKs,
"compare-sdk",
"Compare SDK content in JSON file"),
clEnumValN(ActionType::DiagnoseSDKs,
"diagnose-sdk",
"Diagnose SDK content in JSON file"),
clEnumValN(ActionType::DeserializeDiffItems,
"deserialize-diff",
"Deserialize diff items in a JSON file"),
clEnumValN(ActionType::DeserializeSDK,
"deserialize-sdk",
"Deserialize sdk digester in a JSON file"),
clEnumValN(ActionType::FindUsr,
"find-usr",
"Find USR for decls by given condition"),
clEnumValN(ActionType::GenerateNameCorrectionTemplate,
"generate-name-correction",
"Generate name correction template")));
static llvm::cl::list<std::string>
SDKJsonPaths("input-paths",
llvm::cl::desc("The SDK contents under comparison"));
static llvm::cl::list<std::string>
ApisPrintUsrs("api-usrs",
llvm::cl::desc("The name of APIs to print their usrs, "
"e.g. Type::Function"));
static llvm::cl::opt<std::string>
IgnoreRemovedDeclUSRs("ignored-usrs",
llvm::cl::desc("the file containing USRs of removed decls "
"that the digester should ignore"));
static llvm::cl::opt<std::string>
SwiftVersion("swift-version",
llvm::cl::desc("The Swift compiler version to invoke"));
static llvm::cl::opt<bool>
OutputInJson("json", llvm::cl::desc("Print output in JSON format."));
static llvm::cl::opt<bool>
AvoidLocation("avoid-location",
llvm::cl::desc("Avoid serializing the file paths of SDK nodes."));
static llvm::cl::opt<std::string>
LocationFilter("location",
llvm::cl::desc("Filter nodes with the given location."));
} // namespace options
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;
};
template<typename T>
bool contains(std::vector<T*> &container, T *instance) {
return std::find(container.begin(), container.end(), instance) != container.end();
}
template<typename T>
bool contains(ArrayRef<T> container, T instance) {
return std::find(container.begin(), container.end(), instance) != container.end();
}
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<SDKNode*> Left;
ArrayRef<SDKNode*> Right;
MatchedNodeListener &Listener;
public:
SequentialNodeMatcher(ArrayRef<SDKNode*> Left,
ArrayRef<SDKNode*> 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<bool(NodePtr, NodePtr)> CanMatch;
llvm::function_ref<bool(NodeMatch, NodeMatch)> IsFirstMatchBetter;
NodeMatchReason Reason;
MatchedNodeListener &Listener;
llvm::SmallPtrSet<NodePtr, 16> MatchedRight;
bool internalCanMatch(NodePtr L, NodePtr R) {
return MatchedRight.count(R) == 0 && CanMatch(L, R);
}
Optional<NodePtr> findBestMatch(NodePtr Pin, NodeVector& Candidates) {
Optional<NodePtr> Best;
for (auto Can : Candidates) {
if (!internalCanMatch(Pin, Can))
continue;
if (!Best.hasValue() ||
IsFirstMatchBetter({Pin, Can}, {Pin, Best.getValue()}))
Best = Can;
}
return Best;
}
public:
BestMatchMatcher(NodeVector &Left, NodeVector &Right,
llvm::function_ref<bool(NodePtr, NodePtr)> CanMatch,
llvm::function_ref<bool(NodeMatch, NodeMatch)> 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.getValue());
Listener.foundMatch(L, Best.getValue(), 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 (contains(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_lower(R->getName()) == 0) {
R->annotate(NodeAnnotation::GetterToProperty);
} else if (R->getName().startswith("get") &&
R->getName().substr(3).compare_lower(A->getName()) == 0) {
R->annotate(NodeAnnotation::GetterToProperty);
} else if (R->getName().startswith("set") &&
R->getName().substr(3).compare_lower(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().startswith("c:@Ea@");
}
static bool isNominalEnum(SDKNodeDecl *N) {
return N->getKind() == SDKNodeKind::DeclType &&
N->getUsr().startswith("c:@E@");
}
static Optional<StringRef> getLastPartOfUsr(SDKNodeDecl *N) {
auto LastPartIndex = N->getUsr().find_last_of('@');
if (LastPartIndex == StringRef::npos)
return None;
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<SDKNodeDeclVar>(Child)) {
auto LastPartOfA = getLastPartOfUsr(VC);
if (LastPartOfA && LastPartOfR.getValue() == LastPartOfA.getValue()) {
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.getValue() == LastA.getValue()) {
foundMatch(R, A, NodeMatchReason::Name);
return true;
}
return false;
}
static bool isNameTooSimple(StringRef N) {
static std::vector<std::string> 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).startswith(RR) || ((StringRef)RR).startswith(LL))
return true;
if (((StringRef)LL).startswith((llvm::Twine("ns") + RR).str()) ||
((StringRef)RR).startswith((llvm::Twine("ns") + LL).str()))
return true;
if (((StringRef)LL).endswith(RR) || ((StringRef)RR).endswith(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<SDKNodeDeclConstructor>(L))
return false;
if (auto LD = dyn_cast<SDKNodeDecl>(L)) {
auto *RD = R->getAs<SDKNodeDecl>();
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<SDKNodeDecl>();
auto *M2Right = Match2.Right->getAs<SDKNodeDecl>();
// 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<SDKNodeDecl>(P); };
for (auto R : SDKNodeVectorViewer(Removed, IsDecl)) {
for (auto A : SDKNodeVectorViewer(Added, IsDecl)) {
auto RD = R->getAs<SDKNodeDecl>();
auto AD = A->getAs<SDKNodeDecl>();
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 (!contains(RemovedMatched, Remain))
RenameLeft.push_back(Remain);
}
for (auto Remain : Added) {
if (!contains(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<SDKNode*> Left;
ArrayRef<SDKNode*> Right;
MatchedNodeListener &Listener;
enum class NameMatchKind {
USR,
PrintedName,
PrintedNameAndUSR,
};
static bool isUSRSame(SDKNode *L, SDKNode *R) {
auto *LD = dyn_cast<SDKNodeDecl>(L);
auto *RD = dyn_cast<SDKNodeDecl>(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.
Optional<NameMatchKind> getNameMatchKind(SDKNode *L, SDKNode *R) {
if (L->getKind() != R->getKind())
return None;
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 None;
}
struct NameMatchCandidate {
SDKNode *Node;
NameMatchKind Kind;
};
// Get the priority for the favored name match kind. Favored name match kind
// locats before less favored ones.
ArrayRef<NameMatchKind> 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<NameMatchCandidate> Candidates,
ArrayRef<NameMatchKind> Kinds) {
for (auto Kind : Kinds)
for (auto &Can : Candidates)
if (Kind == Can.Kind)
return Can.Node;
return nullptr;
}
public:
SameNameNodeMatcher(ArrayRef<SDKNode*> Left, ArrayRef<SDKNode*> 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<NameMatchCandidate> Candidates;
for (auto *RN : Right) {
// If RN has matched before, ignore it.
if (contains(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.getValue()});
}
// 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 (!contains(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() {}
};
static void detectRename(SDKNode *L, SDKNode *R) {
if (L->getKind() == R->getKind() && isa<SDKNodeDecl>(L) &&
L->getPrintedName() != R->getPrintedName()) {
L->annotate(NodeAnnotation::Rename);
L->annotate(NodeAnnotation::RenameOldName, L->getPrintedName());
L->annotate(NodeAnnotation::RenameNewName, R->getPrintedName());
}
}
static bool isOwnershipEquivalent(ReferenceOwnership Left,
ReferenceOwnership Right) {
if (Left == Right)
return true;
if (Left == ReferenceOwnership::Unowned && Right == ReferenceOwnership::Weak)
return true;
if (Left == ReferenceOwnership::Weak && Right == ReferenceOwnership::Unowned)
return true;
return false;
}
}// End of anonymous namespace
void swift::ide::api::SDKNodeDeclType::diagnose(SDKNode *Right) {
SDKNodeDecl::diagnose(Right);
auto *R = dyn_cast<SDKNodeDeclType>(Right);
if (!R)
return;
if (getDeclKind() != R->getDeclKind()) {
emitDiag(diag::decl_kind_changed, getDeclKindStr(R->getDeclKind()));
return;
}
assert(getDeclKind() == R->getDeclKind());
auto DKind = getDeclKind();
switch (DKind) {
case DeclKind::Class: {
auto LSuperClass = getSuperClassName();
auto RSuperClass = R->getSuperClassName();
if (!LSuperClass.empty() && LSuperClass != RSuperClass) {
if (RSuperClass.empty()) {
emitDiag(diag::super_class_removed, LSuperClass);
} else if (!contains(R->getClassInheritanceChain(), LSuperClass)) {
emitDiag(diag::super_class_changed, LSuperClass, RSuperClass);
}
}
break;
}
default:
break;
}
}
void swift::ide::api::SDKNodeDeclAbstractFunc::diagnose(SDKNode *Right) {
SDKNodeDecl::diagnose(Right);
auto *R = dyn_cast<SDKNodeDeclAbstractFunc>(Right);
if (!R)
return;
if (!isThrowing() && R->isThrowing()) {
emitDiag(diag::decl_new_attr, Ctx.buffer("throwing"));
}
if (Ctx.checkingABI()) {
if (reqNewWitnessTableEntry() != R->reqNewWitnessTableEntry()) {
emitDiag(diag::decl_new_witness_table_entry, reqNewWitnessTableEntry());
}
}
}
void swift::ide::api::SDKNodeDeclFunction::diagnose(SDKNode *Right) {
SDKNodeDeclAbstractFunc::diagnose(Right);
auto *R = dyn_cast<SDKNodeDeclFunction>(Right);
if (!R)
return;
if (getSelfAccessKind() != R->getSelfAccessKind()) {
emitDiag(diag::func_self_access_change, getSelfAccessKind(),
R->getSelfAccessKind());
}
if (Ctx.checkingABI()) {
if (hasFixedBinaryOrder() != R->hasFixedBinaryOrder()) {
emitDiag(diag::func_has_fixed_order_change, hasFixedBinaryOrder());
}
}
}
void swift::ide::api::SDKNodeDeclSubscript::diagnose(SDKNode *Right) {
SDKNodeDeclAbstractFunc::diagnose(Right);
auto *R = dyn_cast<SDKNodeDeclSubscript>(Right);
if (!R)
return;
if (hasSetter() && !R->hasSetter()) {
emitDiag(diag::removed_setter);
}
}
void swift::ide::api::SDKNodeDecl::diagnose(SDKNode *Right) {
SDKNode::diagnose(Right);
auto *RD = dyn_cast<SDKNodeDecl>(Right);
if (!RD)
return;
detectRename(this, RD);
if (isOpen() && !RD->isOpen()) {
emitDiag(diag::no_longer_open);
}
// Diagnose static attribute change.
if (isStatic() ^ RD->isStatic()) {
emitDiag(diag::decl_new_attr, Ctx.buffer(isStatic() ? "not static" :
"static"));
}
// Diagnose ownership change.
if (!isOwnershipEquivalent(getReferenceOwnership(),
RD->getReferenceOwnership())) {
auto getOwnershipDescription = [&](swift::ReferenceOwnership O) {
if (O == ReferenceOwnership::Strong)
return Ctx.buffer("strong");
return keywordOf(O);
};
emitDiag(diag::decl_attr_change,
getOwnershipDescription(getReferenceOwnership()),
getOwnershipDescription(RD->getReferenceOwnership()));
}
// Diagnose generic signature change
if (getGenericSignature() != RD->getGenericSignature()) {
emitDiag(diag::generic_sig_change,
getGenericSignature(), RD->getGenericSignature());
}
if (isOptional() != RD->isOptional()) {
if (Ctx.checkingABI()) {
// Both adding/removing optional is ABI-breaking.
emitDiag(diag::optional_req_changed, isOptional());
} else if (isOptional()) {
// Removing optional is source-breaking.
emitDiag(diag::optional_req_changed, isOptional());
}
}
// Check if some attributes with ABI/API-impact have been added/removed.
for (auto &Info: Ctx.getBreakingAttributeInfo()) {
if (hasDeclAttribute(Info.Kind) != RD->hasDeclAttribute(Info.Kind)) {
auto Desc = hasDeclAttribute(Info.Kind) ?
Ctx.buffer((llvm::Twine("without ") + Info.Content).str()):
Ctx.buffer((llvm::Twine("with ") + Info.Content).str());
emitDiag(diag::decl_new_attr, Desc);
}
}
if (Ctx.checkingABI()) {
if (hasFixedBinaryOrder() && RD->hasFixedBinaryOrder() &&
getFixedBinaryOrder() != RD->getFixedBinaryOrder()) {
emitDiag(diag::decl_reorder, getFixedBinaryOrder(),
RD->getFixedBinaryOrder());
}
}
}
void swift::ide::api::SDKNodeDeclOperator::diagnose(SDKNode *Right) {
SDKNodeDecl::diagnose(Right);
auto *RO = dyn_cast<SDKNodeDeclOperator>(Right);
if (!RO)
return;
if (getDeclKind() != RO->getDeclKind()) {
emitDiag(diag::decl_kind_changed, getDeclKindStr(RO->getDeclKind()));
}
}
void swift::ide::api::SDKNodeDeclVar::diagnose(SDKNode *Right) {
SDKNodeDecl::diagnose(Right);
auto *RV = dyn_cast<SDKNodeDeclVar>(Right);
if (!RV)
return;
if (getSetter() && !RV->getSetter()) {
emitDiag(diag::removed_setter);
}
if (Ctx.checkingABI()) {
if (hasFixedBinaryOrder() != RV->hasFixedBinaryOrder()) {
emitDiag(diag::var_has_fixed_order_change, hasFixedBinaryOrder());
}
if (isLet() != RV->isLet()) {
emitDiag(diag::var_let_changed, isLet());
}
}
}
static bool shouldDiagnoseType(SDKNodeType *T) {
return T->isTopLevelType();
}
void swift::ide::api::SDKNodeType::diagnose(SDKNode *Right) {
SDKNode::diagnose(Right);
auto *RT = dyn_cast<SDKNodeType>(Right);
if (!RT || !shouldDiagnoseType(this))
return;
assert(isTopLevelType());
// Diagnose type witness changes when diagnosing ABI breakages.
if (auto *Wit = dyn_cast<SDKNodeTypeWitness>(getParent())) {
auto *Conform = Wit->getParent()->getAs<SDKNodeConformance>();
if (Ctx.checkingABI() && getPrintedName() != RT->getPrintedName()) {
Conform->getNominalTypeDecl()->emitDiag(diag::type_witness_change,
Wit->getWitnessedTypeName(),
getPrintedName(),
RT->getPrintedName());
}
return;
}
StringRef Descriptor = getTypeRoleDescription();
assert(isa<SDKNodeDecl>(getParent()));
auto LParent = cast<SDKNodeDecl>(getParent());
assert(LParent->getKind() == RT->getParent()->getAs<SDKNodeDecl>()->getKind());
if (getPrintedName() != RT->getPrintedName()) {
LParent->emitDiag(diag::decl_type_change,
Descriptor, getPrintedName(), RT->getPrintedName());
}
if (hasDefaultArgument() && !RT->hasDefaultArgument()) {
LParent->emitDiag(diag::default_arg_removed, Descriptor);
}
if (getParamValueOwnership() != RT->getParamValueOwnership()) {
getParent()->getAs<SDKNodeDecl>()->emitDiag(diag::param_ownership_change,
getTypeRoleDescription(),
getParamValueOwnership(),
RT->getParamValueOwnership());
}
}
void swift::ide::api::SDKNodeTypeFunc::diagnose(SDKNode *Right) {
SDKNode::diagnose(Right);
auto *RT = dyn_cast<SDKNodeTypeFunc>(Right);
if (!RT || !shouldDiagnoseType(this))
return;
assert(isTopLevelType());
if (Ctx.checkingABI() && isEscaping() != RT->isEscaping()) {
getParent()->getAs<SDKNodeDecl>()->emitDiag(diag::func_type_escaping_changed,
getTypeRoleDescription(),
isEscaping());
}
}
namespace {
// 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<> ProtocolReqWhitelist;
static void printSpaces(llvm::raw_ostream &OS, SDKNode *N) {
assert(N);
for (auto P = N; !isa<SDKNodeRoot>(P); P = P->getParent())
OS << " ";
}
static void debugMatch(SDKNode *Left, SDKNode *Right, NodeMatchReason Reason,
llvm::raw_ostream &OS) {
if (Left && !isa<SDKNodeDecl>(Left))
return;
if (Right && !isa<SDKNodeDecl>(Right))
return;
StringRef Arrow = " <--------> ";
switch (Reason) {
case NodeMatchReason::Added:
printSpaces(OS, Right);
OS << "<NULL>" << Arrow << Right->getPrintedName() << "\n";
return;
case NodeMatchReason::Removed:
printSpaces(OS, Left);
OS << Left->getPrintedName() << Arrow << "<NULL>\n";
return;
default:
printSpaces(OS, Left);
OS << Left->getPrintedName() << Arrow << Right->getPrintedName() << "\n";
return;
}
}
public:
PrunePass(SDKContext &Ctx): Ctx(Ctx), UpdateMap(Ctx.getNodeUpdateMap()) {}
PrunePass(SDKContext &Ctx, llvm::StringSet<> prWhitelist):
Ctx(Ctx),
UpdateMap(Ctx.getNodeUpdateMap()),
ProtocolReqWhitelist(std::move(prWhitelist)) {}
void foundMatch(NodePtr Left, NodePtr Right, NodeMatchReason Reason) override {
if (options::Verbose)
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<SDKNodeDecl>(Right)) {
if (D->hasFixedBinaryOrder()) {
D->emitDiag(diag::decl_added);
}
}
}
// Complain about added protocol requirements
if (auto *D = dyn_cast<SDKNodeDecl>(Right)) {
if (D->isNonOptionalProtocolRequirement()) {
bool ShouldComplain = !D->isOverriding();
// We should allow added associated types with default.
if (auto ATD = dyn_cast<SDKNodeDeclAssociatedType>(D)) {
if (ATD->getDefault())
ShouldComplain = false;
}
if (ShouldComplain &&
ProtocolReqWhitelist.count(D->getParent()->getAs<SDKNodeDecl>()->
getFullyQualifiedName())) {
// Ignore protocol requirement additions if the protocol has been added
// to the whitelist.
ShouldComplain = false;
}
if (ShouldComplain)
D->emitDiag(diag::protocol_req_added);
}
}
// Diagnose an inherited protocol has been added.
if (auto *Conf = dyn_cast<SDKNodeConformance>(Right)) {
auto *TD = Conf->getNominalTypeDecl();
if (TD->isProtocol()) {
TD->emitDiag(diag::conformance_added, Conf->getName());
}
}
return;
case NodeMatchReason::Removed:
assert(!Right);
Left->annotate(NodeAnnotation::Removed);
if (auto *LT = dyn_cast<SDKNodeType>(Left)) {
if (auto *AT = dyn_cast<SDKNodeDeclAssociatedType>(LT->getParent())) {
AT->emitDiag(diag::default_associated_type_removed,
LT->getPrintedName());
}
}
// Diagnose a protocol conformance has been removed.
if (auto *Conf = dyn_cast<SDKNodeConformance>(Left)) {
auto *TD = Conf->getNominalTypeDecl();
TD->emitDiag(diag::conformance_removed,
Conf->getName(),
TD->isProtocol());
}
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<SDKNodeType>(Left) && isa<SDKNodeType>(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<SDKNodeDeclType>(Left)->getConformances();
auto RConf = cast<SDKNodeDeclType>(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::DeclSubscript:
case SDKNodeKind::DeclAssociatedType:
case SDKNodeKind::DeclFunction:
case SDKNodeKind::DeclSetter:
case SDKNodeKind::DeclGetter:
case SDKNodeKind::DeclConstructor:
case SDKNodeKind::DeclTypeAlias:
case SDKNodeKind::TypeFunc:
case SDKNodeKind::TypeNominal:
case SDKNodeKind::TypeAlias: {
// 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::DeclVar: {
auto *LVar = dyn_cast<SDKNodeDeclVar>(Left);
auto *RVar = dyn_cast<SDKNodeDeclVar>(Right);
// Match property type.
singleMatch(LVar->getType(), RVar->getType(), *this);
// Match property getter function.
singleMatch(LVar->getGetter(), RVar->getGetter(), *this);
// Match property setter function.
singleMatch(LVar->getSetter(), RVar->getSetter(), *this);
break;
}
}
}
void pass(NodePtr Left, NodePtr Right) override {
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<SDKNodeDecl>(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<SDKNodeDecl>()->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::DeclGetter &&
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<SDKNodeDeclTypeAlias>(node);
if (!alias)
return;
const SDKNodeType* aliasType = alias->getUnderlyingType();
for (auto *counter: rightRoot->getDescendantsByUsr(alias->getUsr())) {
if (auto DT = dyn_cast<SDKNodeDeclType>(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<bool(NodePtr)> Predicate;
public:
SearchVisitor(llvm::function_ref<bool(NodePtr)> 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<SDKNodeTypeAlias>(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<SDKNodeType>(), Raw))
return StringRef();
auto* RKey = dyn_cast<SDKNodeTypeNominal>(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<SDKNodeDeclType>(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<SDKNodeTypeNominal>(L);
auto *Right = dyn_cast<SDKNodeTypeNominal>(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]? Chnage
KeyChangedTo =
detectDictionaryKeyChangeInternal(L->getOnlyChild()->getAs<SDKNodeType>(),
R->getOnlyChild()->getAs<SDKNodeType>(),
Raw);
} else {
// Detect [String: Any] to [StringRepresentableStruct: Any] Chnage
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<SDKNodeTypeNominal>(L);
auto *Right = dyn_cast<SDKNodeTypeNominal>(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]? Chnage
KeyChangedTo =
detectArrayMemberChangeInternal(L->getOnlyChild()->getAs<SDKNodeType>(),
R->getOnlyChild()->getAs<SDKNodeType>(),
Raw);
} else {
// Detect [String] to [StringRepresentableStruct] Chnage
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<SDKNodeType>(),
R->getOnlyChild()->getAs<SDKNodeType>(),
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<SDKNodeType>(Left);
auto *Counter = dyn_cast<SDKNodeType>(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<SDKNodeDeclAbstractFunc>(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<typename T>
void removeRedundantAndSort(std::vector<T> &Diffs) {
std::set<T> DiffSet(Diffs.begin(), Diffs.end());
Diffs.assign(DiffSet.begin(), DiffSet.end());
std::sort(Diffs.begin(), Diffs.end());
}
template<typename T>
void serializeDiffs(llvm::raw_ostream &Fs, std::vector<T> &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<NodeAnnotation> 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<SDKNodeDecl>(Node);
if (!Parent) {
if (auto TN = dyn_cast<SDKNodeType>(Node)) {
Parent = TN->getClosestParentDecl();
}
}
if (!Parent)
return;
if (doesAncestorHaveTypeRewritten())
return;
handleAnnotations(Node, Parent,
isa<SDKNodeType>(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<int> 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<int> &Indexes) {
for (unsigned I = Ancestors.size(); I > 0 && (I == Ancestors.size() ||
isa<SDKNodeType>(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<SDKNodeDecl*> &Results);
std::set<SDKNodeDecl*> 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<SDKNodeDecl*> &Results) {
if (auto *D = dyn_cast<SDKNodeDecl>(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);
}
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(diag::moved_decl,
Ctx.buffer((Twine(getDeclKindStr(Added->getDeclKind())) + " " +
Added->getFullyQualifiedName()).str()));
return;
}
}
// If we can find a hoisted member for this removed delcaration, 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(diag::renamed_decl,
Ctx.buffer((Twine(getDeclKindStr(Node->getDeclKind())) + " " +
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(diag::raw_type_change,
Node->getAs<SDKNodeDeclTypeAlias>()->getUnderlyingType()->getPrintedName(),
TypeAliasUpdateMap[(SDKNode*)Node]->getAs<SDKNodeDeclType>()->
getRawValueType()->getPrintedName());
return;
}
// We should exlude those declarations that are pulled up to the super classes.
bool FoundInSuperclass = false;
if (auto PD = dyn_cast<SDKNodeDecl>(Node->getParent())) {
if (PD->isAnnotatedAs(NodeAnnotation::Updated)) {
// Get the updated counterpart of the parent decl.
if (auto RTD = dyn_cast<SDKNodeDeclType>(UpdateMap.
findUpdateCounterpart(PD))) {
// Look up by the printed name in the counterpart.
FoundInSuperclass =
RTD->lookupChildByPrintedName(Node->getPrintedName()).hasValue();
}
}
}
if (FoundInSuperclass)
return;
Node->emitDiag(diag::removed_decl, Node->isDeprecated());
return;
}
case NodeAnnotation::Rename: {
auto *Count = UpdateMap.findUpdateCounterpart(Node)->getAs<SDKNodeDecl>();
Node->emitDiag(diag::renamed_decl,
Ctx.buffer((Twine(getDeclKindStr(Count->getDeclKind())) + " " +
Count->getFullyQualifiedName()).str()));
return;
}
default:
return;
}
}
void DiagnosisEmitter::visitDecl(SDKNodeDecl *Node) {
std::vector<NodeAnnotation> Scratch;
for (auto Anno : Node->getAnnotations(Scratch))
handle(Node, Anno);
}
void DiagnosisEmitter::visit(NodePtr Node) {
if (auto *DNode = dyn_cast<SDKNodeDecl>(Node)) {
visitDecl(DNode);
}
}
typedef std::vector<NoEscapeFuncParam> 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<SDKNodeTypeFunc>()->isEscaping())
return;
auto Parent = Node->getParent();
if (auto ParentFunc = dyn_cast<SDKNodeDeclAbstractFunc>(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;
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 Optional<uint8_t> findSelfIndex(SDKNode* Node) {
if (auto func = dyn_cast<SDKNodeDeclAbstractFunc>(Node)) {
return func->getSelfIndexOptional();
} else if (auto vd = dyn_cast<SDKNodeDeclVar>(Node)) {
for (auto &C : vd->getChildren()) {
if (isa<SDKNodeDeclAbstractFunc>(C)) {
if (auto Result = findSelfIndex(C))
return Result;
}
}
}
return None;
}
/// 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<SDKNodeRoot>(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<SDKNodeDecl>()->getUsr(),
rightParent->getKind() == SDKNodeKind::Root ?
StringRef() : rightParent->getAs<SDKNodeDecl>()->getFullyQualifiedName(),
right->getPrintedName(), findSelfIndex(right), None,
leftParent->getKind() == SDKNodeKind::Root ?
StringRef() : leftParent->getAs<SDKNodeDecl>()->getFullyQualifiedName(),
left->getPrintedName()
};
out.emplace_back(item);
Detector.workOn(left, right);
}
}
static int diagnoseModuleChange(StringRef LeftPath, StringRef RightPath,
StringRef OutputPath,
CheckerOptions Opts,
llvm::StringSet<> ProtocolReqWhitelist) {
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::raw_ostream *OS = &llvm::errs();
std::unique_ptr<llvm::raw_ostream> FileOS;
if (!OutputPath.empty()) {
std::error_code EC;
FileOS.reset(new llvm::raw_fd_ostream(OutputPath, EC, llvm::sys::fs::F_None));
OS = FileOS.get();
}
ModuleDifferDiagsConsumer PDC(true, *OS);
SDKContext Ctx(Opts);
Ctx.getDiags().addConsumer(PDC);
SwiftDeclCollector LeftCollector(Ctx);
LeftCollector.deSerialize(LeftPath);
SwiftDeclCollector RightCollector(Ctx);
RightCollector.deSerialize(RightPath);
auto LeftModule = LeftCollector.getSDKRoot();
auto RightModule = RightCollector.getSDKRoot();
TypeAliasDiffFinder(LeftModule, RightModule,
Ctx.getTypeAliasUpdateMap()).search();
PrunePass Prune(Ctx, std::move(ProtocolReqWhitelist));
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 0;
}
static void populateAliasChanges(NodeMap &AliasMap, DiffVector &AllItems,
const bool isRevert) {
for (auto Pair: AliasMap) {
auto UnderlyingType = Pair.first->getAs<SDKNodeDeclTypeAlias>()->
getUnderlyingType()->getPrintedName();
auto RawType = AliasMap[(SDKNode*)Pair.first]->getAs<SDKNodeDeclType>()->
getRawValueType()->getPrintedName();
if (isRevert) {
auto *D = Pair.second->getAs<SDKNodeDecl>();
AllItems.emplace_back(SDKNodeKind::DeclType,
NodeAnnotation::RevertTypeAliasDeclToRawRepresentable, "0",
D->getUsr(), "", RawType, UnderlyingType, D->getModuleName());
} else {
auto *D = Pair.first->getAs<SDKNodeDecl>();
AllItems.emplace_back(SDKNodeKind::DeclTypeAlias,
NodeAnnotation::TypeAliasDeclToRawRepresentable, "0",
D->getUsr(), "", UnderlyingType, RawType, D->getModuleName());
}
}
}
static int compareSDKs(StringRef LeftPath, StringRef RightPath,
StringRef DiffPath,
llvm::StringSet<> &IgnoredRemoveUsrs, CheckerOptions Opts) {
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";
ModuleDifferDiagsConsumer PDC(false);
SDKContext Ctx(Opts);
Ctx.getDiags().addConsumer(PDC);
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();
// 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);
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.find(Item.LeftUsr) != IgnoredRemoveUsrs.end();
}), AllItems.end());
NoEscapeFuncParamVector AllNoEscapingFuncs;
NoEscapingFuncEmitter::collectDiffItems(RightModule, AllNoEscapingFuncs);
llvm::errs() << "Dumping diff to " << DiffPath << '\n';
std::vector<OverloadedFuncInfo> Overloads;
// OverloadMemberFunctionEmitter::collectDiffItems(RightModule, Overloads);
auto &typeMemberDiffs = Ctx.getTypeMemberDiffs();
std::error_code EC;
llvm::raw_fd_ostream Fs(DiffPath, EC, llvm::sys::fs::F_None);
removeRedundantAndSort(AllItems);
removeRedundantAndSort(typeMemberDiffs);
removeRedundantAndSort(AllNoEscapingFuncs);
removeRedundantAndSort(Overloads);
if (options::OutputInJson) {
std::vector<APIDiffItem*> TotalItems;
std::transform(AllItems.begin(), AllItems.end(),
std::back_inserter(TotalItems),
[](CommonDiffItem &Item) { return &Item; });
std::transform(typeMemberDiffs.begin(), typeMemberDiffs.end(),
std::back_inserter(TotalItems),
[](TypeMemberDiffItem &Item) { return &Item; });
std::transform(AllNoEscapingFuncs.begin(), AllNoEscapingFuncs.end(),
std::back_inserter(TotalItems),
[](NoEscapeFuncParam &Item) { return &Item; });
std::transform(Overloads.begin(), Overloads.end(),
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 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.startswith("// ")) // comment.
continue;
Lines.insert(Line);
}
return 0;
}
// This function isn't referenced outside its translation unit, but it
// can't use the "static" keyword because its address is used for
// getMainExecutable (since some platforms don't support taking the
// address of main, and some platforms can't implement getMainExecutable
// without being given the address of a function in the main executable).
void anchorForGetMainExecutable() {}
static int prepareForDump(const char *Main,
CompilerInvocation &InitInvok,
llvm::StringSet<> &Modules) {
InitInvok.setMainExecutablePath(fs::getMainExecutable(Main,
reinterpret_cast<void *>(&anchorForGetMainExecutable)));
InitInvok.setModuleName("swift_ide_test");
if (!options::SDK.empty()) {
InitInvok.setSDKPath(options::SDK);
} else if (const char *SDKROOT = getenv("SDKROOT")) {
InitInvok.setSDKPath(SDKROOT);
} else {
llvm::errs() << "Provide '-sdk <path>' option or run with 'xcrun -sdk <..>\
swift-api-digester'\n";
return 1;
}
if (!options::Triple.empty())
InitInvok.setTargetTriple(options::Triple);
InitInvok.getClangImporterOptions().ModuleCachePath =
options::ModuleCachePath;
if (!options::SwiftVersion.empty()) {
using version::Version;
bool isValid = false;
if (auto Version = Version::parseVersionString(options::SwiftVersion,
SourceLoc(), nullptr)) {
if (auto Effective = Version.getValue().getEffectiveLanguageVersion()) {
InitInvok.getLangOptions().EffectiveLanguageVersion = *Effective;
isValid = true;
}
}
if (!isValid) {
llvm::errs() << "Unsupported Swift Version.\n";
return 1;
}
}
if (!options::ResourceDir.empty()) {
InitInvok.setRuntimeResourcePath(options::ResourceDir);
}
std::vector<SearchPathOptions::FrameworkSearchPath> FramePaths;
for (const auto &path : options::FrameworkPaths) {
FramePaths.push_back({path, /*isSystem=*/false});
}
for (const auto &path : options::CCSystemFrameworkPaths) {
FramePaths.push_back({path, /*isSystem=*/true});
}
InitInvok.setFrameworkSearchPaths(FramePaths);
InitInvok.setImportSearchPaths(options::ModuleInputPaths);
if (!options::ModuleList.empty()) {
if (readFileLineByLine(options::ModuleList, Modules))
return 1;
}
for (auto M : options::ModuleNames) {
Modules.insert(M);
}
if (Modules.empty()) {
llvm::errs() << "Need to specify -include-all or -module <name>\n";
return 1;
}
return 0;
}
static void readIgnoredUsrs(llvm::StringSet<> &IgnoredUsrs) {
StringRef Path = options::IgnoreRemovedDeclUSRs;
if (Path.empty())
return;
if (!fs::exists(Path)) {
llvm::errs() << Path << " does not exist.\n";
return;
}
readFileLineByLine(Path, IgnoredUsrs);
}
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::F_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::F_None);
std::set<NameCorrectionInfo> Result;
for (auto *Item: Store.getAllDiffItems()) {
if (auto *CI = dyn_cast<CommonDiffItem>(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<NameCorrectionInfo> Vec;
Vec.insert(Vec.end(), Result.begin(), Result.end());
APIDiffItemStore::serialize(FS, Vec);
return EC.value();
}
static CheckerOptions getCheckOpts() {
CheckerOptions Opts;
Opts.AvoidLocation = options::AvoidLocation;
Opts.ABI = options::Abi;
Opts.Verbose = options::Verbose;
Opts.AbortOnModuleLoadFailure = options::AbortOnModuleLoadFailure;
Opts.LocationFilter = options::LocationFilter;
Opts.PrintModule = options::PrintModule;
Opts.SwiftOnly = options::SwiftOnly;
return Opts;
}
int main(int argc, char *argv[]) {
PROGRAM_START(argc, argv);
INITIALIZE_LLVM();
llvm::cl::ParseCommandLineOptions(argc, argv, "Swift SDK Digester\n");
CompilerInvocation InitInvok;
llvm::StringSet<> Modules;
std::vector<std::string> PrintApis;
llvm::StringSet<> IgnoredUsrs;
readIgnoredUsrs(IgnoredUsrs);
CheckerOptions Opts = getCheckOpts();
for (auto Name : options::ApisPrintUsrs)
PrintApis.push_back(Name);
switch (options::Action) {
case ActionType::DumpSwiftModules:
return (prepareForDump(argv[0], InitInvok, Modules)) ? 1 :
dumpSwiftModules(InitInvok, Modules, options::OutputFile, PrintApis, Opts);
case ActionType::DumpSDK:
return (prepareForDump(argv[0], InitInvok, Modules)) ? 1 :
dumpSDKContent(InitInvok, Modules, options::OutputFile, Opts);
case ActionType::CompareSDKs:
case ActionType::DiagnoseSDKs: {
if (options::SDKJsonPaths.size() != 2) {
llvm::errs() << "Only two SDK versions can be compared\n";
llvm::cl::PrintHelpMessage();
return 1;
}
llvm::StringSet<> protocolWhitelist;
if (!options::ProtReqWhiteList.empty()) {
if (readFileLineByLine(options::ProtReqWhiteList, protocolWhitelist))
return 1;
}
if (options::Action == ActionType::CompareSDKs)
return compareSDKs(options::SDKJsonPaths[0], options::SDKJsonPaths[1],
options::OutputFile, IgnoredUsrs, Opts);
else
return diagnoseModuleChange(options::SDKJsonPaths[0],
options::SDKJsonPaths[1],
options::OutputFile, Opts,
std::move(protocolWhitelist));
}
case ActionType::DeserializeSDK:
case ActionType::DeserializeDiffItems: {
if (options::SDKJsonPaths.size() != 1) {
llvm::cl::PrintHelpMessage();
return 1;
}
if (options::Action == ActionType::DeserializeDiffItems) {
CompilerInstance CI;
APIDiffItemStore Store(CI.getDiags());
return deserializeDiffItems(Store, options::SDKJsonPaths[0],
options::OutputFile);
} else {
return deserializeSDKDump(options::SDKJsonPaths[0], options::OutputFile,
Opts);
}
}
case ActionType::GenerateNameCorrectionTemplate: {
CompilerInstance CI;
APIDiffItemStore Store(CI.getDiags());
auto &Paths = options::SDKJsonPaths;
for (unsigned I = 0; I < Paths.size(); I ++)
Store.addStorePath(Paths[I]);
return deserializeNameCorrection(Store, options::OutputFile);
}
case ActionType::FindUsr: {
if (options::SDKJsonPaths.size() != 1) {
llvm::cl::PrintHelpMessage();
return 1;
}
return findDeclUsr(options::SDKJsonPaths[0], Opts);
}
case ActionType::None:
llvm::errs() << "Action required\n";
llvm::cl::PrintHelpMessage();
return 1;
}
}