ABI/API checker: diagnose ObjC name changes as breakages

rdar://54797695
This commit is contained in:
Xi Ge
2019-08-28 11:55:54 -07:00
parent 62f947d6ba
commit 6374d3676a
15 changed files with 187 additions and 9 deletions

View File

@@ -96,6 +96,8 @@ ERROR(decl_new_witness_table_entry,none,"%0 now requires %select{|no}1 new witne
ERROR(new_decl_without_intro,none,"%0 is a new API without @available attribute", (StringRef))
ERROR(objc_name_change,none,"%0 has ObjC name change from %1 to %2", (StringRef, StringRef, StringRef))
#ifndef DIAG_NO_UNDEF
# if defined(DIAG)
# undef DIAG

View File

@@ -151,6 +151,7 @@ KEY_STRING(IntroiOS, intro_iOS)
KEY_STRING(IntrotvOS, intro_tvOS)
KEY_STRING(IntrowatchOS, intro_watchOS)
KEY_STRING(Introswift, intro_swift)
KEY_STRING(ObjCName, objc_name)
KEY_STRING_ARR(SuperclassNames, superclassNames)
KEY_STRING_ARR(ToolArgs, tool_arguments)

View File

@@ -3,6 +3,7 @@
public protocol P1 {}
public protocol P2 {}
public protocol P3: P2, P1 {}
@frozen
public struct S1: P1 {
public static func foo1() {}
@@ -123,3 +124,9 @@ public class PlatformIntroClass {}
@available(swift, introduced: 5)
public class SwiftIntroClass {}
@objc(NewObjCClass)
public class SwiftObjcClass {
@objc(ObjCFool:ObjCA:ObjCB:)
public func foo(a:Int, b:Int, c: Int) {}
}

View File

@@ -195,3 +195,9 @@ public class Zoo {
}
public func returnFunctionTypeOwnershipChange() -> (C1) -> () { return { _ in } }
@objc(OldObjCClass)
public class SwiftObjcClass {
@objc(OldObjCFool:OldObjCA:OldObjCB:)
public func foo(a:Int, b:Int, c: Int) {}
}

View File

@@ -202,3 +202,9 @@ public class Zoo {
}
public func returnFunctionTypeOwnershipChange() -> (__owned C1) -> () { return { _ in } }
@objc(NewObjCClass)
public class SwiftObjcClass {
@objc(NewObjCFool:NewObjCA:NewObjCB:)
public func foo(a:Int, b:Int, c: Int) {}
}

View File

@@ -27,7 +27,9 @@ cake: InfixOperator ..*.. has been changed to a PrefixOperator
cake: Protocol ProtocolToEnum has been changed to a Enum
/* Renamed Decls */
cake: Class SwiftObjcClass has ObjC name change from OldObjCClass to NewObjCClass
cake: Func S1.foo5(x:y:) has been renamed to Func foo5(x:y:z:)
cake: Func SwiftObjcClass.foo(a:b:c:) has ObjC name change from OldObjCFool:OldObjCA:OldObjCB: to NewObjCFool:NewObjCA:NewObjCB:
cake: Struct Somestruct2 has been renamed to Struct NSSomestruct2
/* Type Changes */

View File

@@ -22,7 +22,9 @@ cake: InfixOperator ..*.. has been changed to a PrefixOperator
cake: Protocol ProtocolToEnum has been changed to a Enum
/* Renamed Decls */
cake: Class SwiftObjcClass has ObjC name change from OldObjCClass to NewObjCClass
cake: Func S1.foo5(x:y:) has been renamed to Func foo5(x:y:z:)
cake: Func SwiftObjcClass.foo(a:b:c:) has ObjC name change from OldObjCFool:OldObjCA:OldObjCB: to NewObjCFool:NewObjCA:NewObjCB:
cake: Struct Somestruct2 has been renamed to Struct NSSomestruct2
/* Type Changes */

View File

@@ -1431,6 +1431,58 @@
"Available"
]
},
{
"kind": "TypeDecl",
"name": "SwiftObjcClass",
"printedName": "SwiftObjcClass",
"children": [
{
"kind": "Function",
"name": "foo",
"printedName": "foo(a:b:c:)",
"children": [
{
"kind": "TypeNominal",
"name": "Void",
"printedName": "()"
},
{
"kind": "TypeNominal",
"name": "Int",
"printedName": "Swift.Int",
"usr": "s:Si"
},
{
"kind": "TypeNominal",
"name": "Int",
"printedName": "Swift.Int",
"usr": "s:Si"
},
{
"kind": "TypeNominal",
"name": "Int",
"printedName": "Swift.Int",
"usr": "s:Si"
}
],
"declKind": "Func",
"usr": "c:@M@cake@objc(cs)NewObjCClass(im)ObjCFool:ObjCA:ObjCB:",
"moduleName": "cake",
"objc_name": "ObjCFool:ObjCA:ObjCB:",
"declAttributes": [
"ObjC"
],
"funcSelfKind": "NonMutating"
}
],
"declKind": "Class",
"usr": "c:@M@cake@objc(cs)NewObjCClass",
"moduleName": "cake",
"objc_name": "NewObjCClass",
"declAttributes": [
"ObjC"
]
},
{
"kind": "TypeDecl",
"name": "Int",
@@ -1771,5 +1823,5 @@
]
}
],
"json_format_version": 3
"json_format_version": 4
}

View File

@@ -1279,6 +1279,58 @@
"Available"
]
},
{
"kind": "TypeDecl",
"name": "SwiftObjcClass",
"printedName": "SwiftObjcClass",
"children": [
{
"kind": "Function",
"name": "foo",
"printedName": "foo(a:b:c:)",
"children": [
{
"kind": "TypeNominal",
"name": "Void",
"printedName": "()"
},
{
"kind": "TypeNominal",
"name": "Int",
"printedName": "Swift.Int",
"usr": "s:Si"
},
{
"kind": "TypeNominal",
"name": "Int",
"printedName": "Swift.Int",
"usr": "s:Si"
},
{
"kind": "TypeNominal",
"name": "Int",
"printedName": "Swift.Int",
"usr": "s:Si"
}
],
"declKind": "Func",
"usr": "c:@M@cake@objc(cs)NewObjCClass(im)ObjCFool:ObjCA:ObjCB:",
"moduleName": "cake",
"objc_name": "ObjCFool:ObjCA:ObjCB:",
"declAttributes": [
"ObjC"
],
"funcSelfKind": "NonMutating"
}
],
"declKind": "Class",
"usr": "c:@M@cake@objc(cs)NewObjCClass",
"moduleName": "cake",
"objc_name": "NewObjCClass",
"declAttributes": [
"ObjC"
]
},
{
"kind": "TypeDecl",
"name": "Int",
@@ -1622,5 +1674,5 @@
]
}
],
"json_format_version": 3
"json_format_version": 4
}

View File

@@ -31,6 +31,7 @@
"moduleName": "Foo",
"genericSig": "<Self where Self : Foo.AnotherObjcProt>",
"protocolReq": true,
"objc_name": "anotherFunctionFromProt",
"declAttributes": [
"ObjC"
],
@@ -41,6 +42,7 @@
"declKind": "Protocol",
"usr": "c:objc(pl)AnotherObjcProt",
"moduleName": "Foo",
"objc_name": "AnotherObjcProt",
"declAttributes": [
"ObjC"
]
@@ -72,6 +74,7 @@
"usr": "c:objc(cs)ClangInterface(im)someFunction",
"moduleName": "Foo",
"isOpen": true,
"objc_name": "someFunction",
"declAttributes": [
"ObjC"
],
@@ -94,6 +97,7 @@
"moduleName": "Foo",
"overriding": true,
"implicit": true,
"objc_name": "init",
"declAttributes": [
"Override",
"ObjC"
@@ -104,6 +108,7 @@
"usr": "c:objc(cs)ClangInterface",
"moduleName": "Foo",
"isOpen": true,
"objc_name": "ClangInterface",
"declAttributes": [
"ObjC"
],
@@ -154,6 +159,7 @@
"moduleName": "Foo",
"genericSig": "<Self where Self : Foo.ObjcProt>",
"protocolReq": true,
"objc_name": "someFunctionFromProt",
"declAttributes": [
"ObjC"
],
@@ -164,10 +170,11 @@
"declKind": "Protocol",
"usr": "c:objc(pl)ObjcProt",
"moduleName": "Foo",
"objc_name": "ObjcProt",
"declAttributes": [
"ObjC"
]
}
],
"json_format_version": 3
"json_format_version": 4
}

View File

@@ -2,5 +2,5 @@
"kind": "Root",
"name": "TopLevel",
"printedName": "TopLevel",
"json_format_version": 3
"json_format_version": 4
}

View File

@@ -109,7 +109,8 @@ SDKNodeDecl::SDKNodeDecl(SDKNodeInitInfo Info, SDKNodeKind Kind)
SugaredGenericSig(Info.SugaredGenericSig),
FixedBinaryOrder(Info.FixedBinaryOrder),
introVersions({Info.IntromacOS, Info.IntroiOS, Info.IntrotvOS,
Info.IntrowatchOS, Info.Introswift}){}
Info.IntrowatchOS, Info.Introswift}),
ObjCName(Info.ObjCName) {}
SDKNodeType::SDKNodeType(SDKNodeInitInfo Info, SDKNodeKind Kind):
SDKNode(Info, Kind), TypeAttributes(Info.TypeAttrs),
@@ -915,6 +916,8 @@ static bool isSDKNodeEqual(SDKContext &Ctx, const SDKNode &L, const SDKNode &R)
return false;
if (Left->isInternal() != Right->isInternal())
return false;
if (Left->getObjCName() != Right->getObjCName())
return false;
if (Left->hasFixedBinaryOrder() != Right->hasFixedBinaryOrder())
return false;
if (Left->hasFixedBinaryOrder()) {
@@ -1252,6 +1255,16 @@ StringRef SDKContext::getLanguageIntroVersion(Decl *D) {
return getLanguageIntroVersion(D->getDeclContext()->getAsDecl());
}
StringRef SDKContext::getObjcName(Decl *D) {
if (auto *OC = D->getAttrs().getAttribute<ObjCAttr>()) {
if (OC->getName().hasValue()) {
SmallString<32> Buffer;
return buffer(OC->getName()->getString(Buffer));
}
}
return StringRef();
}
SDKNodeInitInfo::SDKNodeInitInfo(SDKContext &Ctx, Type Ty, TypeInitInfo Info) :
Ctx(Ctx), Name(getTypeName(Ctx, Ty, Info.IsImplicitlyUnwrappedOptional)),
PrintedName(getPrintedName(Ctx, Ty, Info.IsImplicitlyUnwrappedOptional)),
@@ -1278,6 +1291,7 @@ SDKNodeInitInfo::SDKNodeInitInfo(SDKContext &Ctx, Decl *D):
IntrotvOS(Ctx.getPlatformIntroVersion(D, PlatformKind::tvOS)),
IntrowatchOS(Ctx.getPlatformIntroVersion(D, PlatformKind::watchOS)),
Introswift(Ctx.getLanguageIntroVersion(D)),
ObjCName(Ctx.getObjcName(D)),
IsImplicit(D->isImplicit()),
IsDeprecated(D->getAttrs().getDeprecated(D->getASTContext())),
IsABIPlaceholder(isABIPlaceholderRecursive(D)) {
@@ -1901,6 +1915,7 @@ void SDKNodeDecl::jsonize(json::Output &out) {
output(out, KeyKind::KK_intro_tvOS, introVersions.tvos);
output(out, KeyKind::KK_intro_watchOS, introVersions.watchos);
output(out, KeyKind::KK_intro_swift, introVersions.swift);
output(out, KeyKind::KK_objc_name, ObjCName);
out.mapOptional(getKeyContent(Ctx, KeyKind::KK_declAttributes).data(), DeclAttributes);
out.mapOptional(getKeyContent(Ctx, KeyKind::KK_fixedbinaryorder).data(), FixedBinaryOrder);
// Strong reference is implied, no need for serialization.

View File

@@ -62,7 +62,7 @@ namespace api {
///
/// When the json format changes in a way that requires version-specific handling, this number should be incremented.
/// This ensures we could have backward compatibility so that version changes in the format won't stop the checker from working.
const uint8_t DIGESTER_JSON_VERSION = 3; // Use fully qualifed type names for all json outputs
const uint8_t DIGESTER_JSON_VERSION = 4; // Add objc_name field
const uint8_t DIGESTER_JSON_DEFAULT_VERSION = 0; // Use this version number for files before we have a version number in json.
class SDKNode;
@@ -172,7 +172,8 @@ class SDKContext {
CheckerOptions Opts;
std::vector<BreakingAttributeInfo> BreakingAttrs;
// The common version of two ABI/API descriptors under comparison.
Optional<uint8_t> CommonVersion;
public:
// Define the set of known identifiers.
#define IDENTIFIER_WITH_NAME(Name, IdStr) StringRef Id_##Name = IdStr;
@@ -204,8 +205,19 @@ public:
DiagnosticEngine &getDiags() {
return Diags;
}
void setCommonVersion(uint8_t Ver) {
assert(!CommonVersion.hasValue());
CommonVersion = Ver;
}
uint8_t getCommonVersion() const {
return *CommonVersion;
}
bool commonVersionAtLeast(uint8_t Ver) const {
return getCommonVersion() >= Ver;
}
StringRef getPlatformIntroVersion(Decl *D, PlatformKind Kind);
StringRef getLanguageIntroVersion(Decl *D);
StringRef getObjcName(Decl *D);
bool isEqual(const SDKNode &Left, const SDKNode &Right);
bool checkingABI() const { return Opts.ABI; }
AccessLevel getAccessLevel(const ValueDecl *VD) const;
@@ -343,6 +355,7 @@ class SDKNodeDecl: public SDKNode {
StringRef SugaredGenericSig;
Optional<uint8_t> FixedBinaryOrder;
PlatformIntroVersion introVersions;
StringRef ObjCName;
protected:
SDKNodeDecl(SDKNodeInitInfo Info, SDKNodeKind Kind);
@@ -379,6 +392,7 @@ public:
bool hasFixedBinaryOrder() const { return FixedBinaryOrder.hasValue(); }
uint8_t getFixedBinaryOrder() const { return *FixedBinaryOrder; }
PlatformIntroVersion getIntroducingVersion() const { return introVersions; }
StringRef getObjCName() const { return ObjCName; }
virtual void jsonize(json::Output &Out) override;
virtual void diagnose(SDKNode *Right) override;

View File

@@ -38,6 +38,7 @@ static StringRef getCategoryName(uint32_t ID) {
case LocalDiagID::decl_kind_changed:
return "/* Moved Decls */";
case LocalDiagID::renamed_decl:
case LocalDiagID::objc_name_change:
return "/* Renamed Decls */";
case LocalDiagID::decl_attr_change:
case LocalDiagID::decl_new_attr:

View File

@@ -845,7 +845,7 @@ void swift::ide::api::SDKNodeDecl::diagnose(SDKNode *Right) {
// Diagnose generic signature change
if (getGenericSignature() != RD->getGenericSignature()) {
// Prefer sugared signature in diagnostics to be more user-friendly.
if (versionAtLeast(2) && RD->versionAtLeast(2) &&
if (Ctx.commonVersionAtLeast(2) &&
getSugaredGenericSignature() != RD->getSugaredGenericSignature()) {
emitDiag(diag::generic_sig_change,
getSugaredGenericSignature(), RD->getSugaredGenericSignature());
@@ -854,6 +854,14 @@ void swift::ide::api::SDKNodeDecl::diagnose(SDKNode *Right) {
getGenericSignature(), RD->getGenericSignature());
}
}
// ObjC name changes are considered breakage
if (getObjCName() != RD->getObjCName()) {
if (Ctx.commonVersionAtLeast(4)) {
emitDiag(diag::objc_name_change, getObjCName(), RD->getObjCName());
}
}
if (isOptional() != RD->isOptional()) {
if (Ctx.checkingABI()) {
// Both adding/removing optional is ABI-breaking.
@@ -2178,6 +2186,8 @@ static int diagnoseModuleChange(SDKContext &Ctx, SDKNodeRoot *LeftModule,
llvm::make_unique<ModuleDifferDiagsConsumer>(true, *OS);
Ctx.getDiags().addConsumer(*pConsumer);
Ctx.setCommonVersion(std::min(LeftModule->getJsonFormatVersion(),
RightModule->getJsonFormatVersion()));
TypeAliasDiffFinder(LeftModule, RightModule,
Ctx.getTypeAliasUpdateMap()).search();
PrunePass Prune(Ctx, std::move(ProtocolReqWhitelist));
@@ -2259,7 +2269,8 @@ static int generateMigrationScript(StringRef LeftPath, StringRef 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";