mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Finish off the log part of _swift_checkClassAndWarnForKeyedArchiving. (#10418)
Logs a warning the first time a problematic class is archived or unarchived. We expect people to actually fix these issues, so the performance of the warning isn't too important. Sample output: [timestamp] Attempting to archive Swift class '_Test.Outer.ArchivedThenUnarchived', which does not have a stable runtime name. [timestamp] Use the 'objc' attribute to ensure that the runtime name will not change: "@objc(_TtCC5_Test5Outer22ArchivedThenUnarchived)" [timestamp] If there are no existing archives containing this class, you can choose a unique, prefixed name instead: "@objc(ABCArchivedThenUnarchived)" Finishes rdar://problem/32414508
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <objc/runtime.h>
|
||||
|
||||
#include "swift/Runtime/HeapObject.h"
|
||||
#include "swift/Runtime/Metadata.h"
|
||||
|
||||
@interface NSKeyedUnarchiver (SwiftAdditions)
|
||||
@@ -10,9 +11,116 @@
|
||||
NS_SWIFT_NAME(_swift_checkClassAndWarnForKeyedArchiving(_:operation:));
|
||||
@end
|
||||
|
||||
static bool isASCIIIdentifierChar(char c) {
|
||||
if (c >= 'a' && c <= 'z') return true;
|
||||
if (c >= 'A' && c <= 'Z') return true;
|
||||
if (c >= '0' && c <= '9') return true;
|
||||
if (c == '_') return true;
|
||||
if (c == '$') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void logIfFirstOccurrence(Class objcClass, void (^log)(void)) {
|
||||
static auto queue = dispatch_queue_create(
|
||||
"SwiftFoundation._checkClassAndWarnForKeyedArchivingQueue",
|
||||
DISPATCH_QUEUE_SERIAL);
|
||||
static NSHashTable *seenClasses = nil;
|
||||
|
||||
dispatch_sync(queue, ^{
|
||||
// Will be NO when seenClasses is still nil.
|
||||
if ([seenClasses containsObject:objcClass])
|
||||
return;
|
||||
|
||||
if (!seenClasses) {
|
||||
NSPointerFunctionsOptions options = 0;
|
||||
options |= NSPointerFunctionsOpaqueMemory;
|
||||
options |= NSPointerFunctionsObjectPointerPersonality;
|
||||
seenClasses = [[NSHashTable alloc] initWithOptions:options capacity:16];
|
||||
}
|
||||
[seenClasses addObject:objcClass];
|
||||
|
||||
// Synchronize logging so that multiple lines aren't interleaved.
|
||||
log();
|
||||
});
|
||||
}
|
||||
|
||||
namespace {
|
||||
class StringRefLite {
|
||||
StringRefLite(const char *data, size_t len) : data(data), length(len) {}
|
||||
public:
|
||||
const char *data;
|
||||
size_t length;
|
||||
|
||||
StringRefLite() : data(nullptr), length(0) {}
|
||||
|
||||
template <size_t N>
|
||||
StringRefLite(const char (&staticStr)[N]) : data(staticStr), length(N) {}
|
||||
|
||||
StringRefLite(swift::TwoWordPair<const char *, uintptr_t> pair)
|
||||
: data(pair.first), length(pair.second) {}
|
||||
|
||||
NS_RETURNS_RETAINED
|
||||
NSString *newNSStringNoCopy() const {
|
||||
return [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(data)
|
||||
length:length
|
||||
encoding:NSUTF8StringEncoding
|
||||
freeWhenDone:NO];
|
||||
}
|
||||
|
||||
const char &operator[](size_t offset) const {
|
||||
assert(offset < length);
|
||||
return data[offset];
|
||||
}
|
||||
|
||||
StringRefLite slice(size_t from, size_t to) const {
|
||||
assert(from <= to);
|
||||
assert(to <= length);
|
||||
return {data + from, to - from};
|
||||
}
|
||||
|
||||
const char *begin() const {
|
||||
return data;
|
||||
}
|
||||
const char *end() const {
|
||||
return data + length;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Assume that a non-generic demangled class name always ends in ".MyClass"
|
||||
/// or ".(MyClass plus extra info)".
|
||||
static StringRefLite findBaseName(StringRefLite demangledName) {
|
||||
size_t end = demangledName.length;
|
||||
size_t parenCount = 0;
|
||||
for (size_t i = end; i != 0; --i) {
|
||||
switch (demangledName[i - 1]) {
|
||||
case '.':
|
||||
if (parenCount == 0) {
|
||||
if (i != end && demangledName[i] == '(')
|
||||
++i;
|
||||
return demangledName.slice(i, end);
|
||||
}
|
||||
break;
|
||||
case ')':
|
||||
parenCount += 1;
|
||||
break;
|
||||
case '(':
|
||||
if (parenCount > 0)
|
||||
parenCount -= 1;
|
||||
break;
|
||||
case ' ':
|
||||
end = i - 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@implementation NSKeyedUnarchiver (SwiftAdditions)
|
||||
|
||||
/// Checks if class \p cls is good for archiving.
|
||||
/// Checks if class \p objcClass is good for archiving.
|
||||
///
|
||||
/// If not, a runtime warning is printed.
|
||||
///
|
||||
@@ -25,20 +133,21 @@
|
||||
/// 2: a Swift non-generic class where adding @objc is valid
|
||||
/// Future versions of this API will return nonzero values for additional cases
|
||||
/// that mean the class shouldn't be archived.
|
||||
+ (int)_swift_checkClassAndWarnForKeyedArchiving:(Class)cls
|
||||
+ (int)_swift_checkClassAndWarnForKeyedArchiving:(Class)objcClass
|
||||
operation:(int)operation {
|
||||
const swift::ClassMetadata *theClass = (swift::ClassMetadata *)cls;
|
||||
using namespace swift;
|
||||
const ClassMetadata *theClass = (ClassMetadata *)objcClass;
|
||||
|
||||
// Is it a (real) swift class?
|
||||
if (!theClass->isTypeMetadata() || theClass->isArtificialSubclass())
|
||||
return 0;
|
||||
|
||||
// Does the class already have a custom name?
|
||||
if (theClass->getFlags() & swift::ClassFlags::HasCustomObjCName)
|
||||
if (theClass->getFlags() & ClassFlags::HasCustomObjCName)
|
||||
return 0;
|
||||
|
||||
// Is it a mangled name?
|
||||
const char *className = class_getName(cls);
|
||||
const char *className = class_getName(objcClass);
|
||||
if (!(className[0] == '_' && className[1] == 'T'))
|
||||
return 0;
|
||||
// Is it a name in the form <module>.<class>? Note: the module name could
|
||||
@@ -48,13 +157,74 @@
|
||||
|
||||
// Is it a generic class?
|
||||
if (theClass->getDescription()->GenericParams.isGeneric()) {
|
||||
// TODO: print a warning
|
||||
logIfFirstOccurrence(objcClass, ^{
|
||||
// Use actual NSStrings to force UTF-8.
|
||||
StringRefLite demangledName = swift_getTypeName(theClass,
|
||||
/*qualified*/true);
|
||||
NSString *demangledString = demangledName.newNSStringNoCopy();
|
||||
NSString *mangledString = NSStringFromClass(objcClass);
|
||||
switch (operation) {
|
||||
case 1:
|
||||
NSLog(@"Attempting to unarchive generic Swift class '%@' with mangled "
|
||||
"runtime name '%@'. Runtime names for generic classes are "
|
||||
"unstable and may change in the future, leading to "
|
||||
"non-decodable data.", demangledString, mangledString);
|
||||
break;
|
||||
default:
|
||||
NSLog(@"Attempting to archive generic Swift class '%@' with mangled "
|
||||
"runtime name '%@'. Runtime names for generic classes are "
|
||||
"unstable and may change in the future, leading to "
|
||||
"non-decodable data.", demangledString, mangledString);
|
||||
break;
|
||||
}
|
||||
NSLog(@"To avoid this failure, create a concrete subclass and register "
|
||||
"it with NSKeyedUnarchiver.setClass(_:forClassName:) instead, "
|
||||
"using the name \"%@\".", mangledString);
|
||||
NSLog(@"If you need to produce archives compatible with older versions "
|
||||
"of your program, use NSKeyedArchiver.setClassName(_:for:) "
|
||||
"as well.");
|
||||
[demangledString release];
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
|
||||
// It's a swift class with a (compiler generated) mangled name, which should
|
||||
// be written into an NSArchive.
|
||||
// TODO: print a warning
|
||||
logIfFirstOccurrence(objcClass, ^{
|
||||
// Use actual NSStrings to force UTF-8.
|
||||
StringRefLite demangledName = swift_getTypeName(theClass,/*qualified*/true);
|
||||
NSString *demangledString = demangledName.newNSStringNoCopy();
|
||||
NSString *mangledString = NSStringFromClass(objcClass);
|
||||
switch (operation) {
|
||||
case 1:
|
||||
NSLog(@"Attempting to unarchive Swift class '%@' with mangled runtime "
|
||||
"name '%@'. The runtime name for this class is unstable and may "
|
||||
"change in the future, leading to non-decodable data.",
|
||||
demangledString, mangledString);
|
||||
break;
|
||||
default:
|
||||
NSLog(@"Attempting to archive Swift class '%@' with mangled runtime "
|
||||
"name '%@'. The runtime name for this class is unstable and may "
|
||||
"change in the future, leading to non-decodable data.",
|
||||
demangledString, mangledString);
|
||||
break;
|
||||
}
|
||||
[demangledString release];
|
||||
NSLog(@"You can use the 'objc' attribute to ensure that the name will not "
|
||||
"change: \"@objc(%@)\"", mangledString);
|
||||
|
||||
StringRefLite baseName = findBaseName(demangledName);
|
||||
// Offer a more generic message if the base name we found doesn't look like
|
||||
// an ASCII identifier. This avoids printing names like "ABCモデル".
|
||||
if (baseName.length == 0 ||
|
||||
!std::all_of(baseName.begin(), baseName.end(), isASCIIIdentifierChar)) {
|
||||
baseName = "MyModel";
|
||||
}
|
||||
|
||||
NSLog(@"If there are no existing archives containing this class, you "
|
||||
"should choose a unique, prefixed name instead: "
|
||||
"\"@objc(ABC%1$.*2$s)\"", baseName.data, (int)baseName.length);
|
||||
});
|
||||
return 2;
|
||||
}
|
||||
@end
|
||||
|
||||
173
test/Interpreter/SDK/check_class_for_archiving_log.swift
Normal file
173
test/Interpreter/SDK/check_class_for_archiving_log.swift
Normal file
@@ -0,0 +1,173 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-build-swift %s -module-name=_Test -import-objc-header %S/Inputs/check_class_for_archiving.h -o %t/a.out
|
||||
// RUN: %target-run %t/a.out 2>&1 | %FileCheck %s
|
||||
|
||||
// REQUIRES: executable_test
|
||||
// REQUIRES: objc_interop
|
||||
|
||||
// This test doesn't use StdlibUnittest because it's primarily concerned with
|
||||
// checking the presence and absence of output.
|
||||
|
||||
import Foundation
|
||||
|
||||
class SwiftClass {}
|
||||
|
||||
func checkArchiving(_ cls: AnyObject.Type) {
|
||||
NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(cls, operation: 0)
|
||||
}
|
||||
func checkUnarchiving(_ cls: AnyObject.Type) {
|
||||
NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(cls, operation: 1)
|
||||
}
|
||||
|
||||
func mark(line: Int32 = #line) {
|
||||
NSLog("--%d--", line)
|
||||
}
|
||||
|
||||
mark() // CHECK: --[[@LINE]]--
|
||||
checkArchiving(SwiftClass.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
|
||||
private class ArchivedTwice {}
|
||||
|
||||
checkArchiving(ArchivedTwice.self)
|
||||
// CHECK-NEXT: Attempting to archive Swift class '_Test.(ArchivedTwice in {{.+}})' with mangled runtime name '_TtC{{.+[0-9]+}}ArchivedTwice'
|
||||
// CHECK-NEXT: @objc(_TtC{{.+[0-9]+}}ArchivedTwice)
|
||||
// CHECK-NEXT: @objc(ABCArchivedTwice)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkArchiving(ArchivedTwice.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
private class UnarchivedTwice {}
|
||||
|
||||
checkUnarchiving(UnarchivedTwice.self)
|
||||
// CHECK-NEXT: Attempting to unarchive Swift class '_Test.(UnarchivedTwice in {{.+}})' with mangled runtime name '_TtC{{.+[0-9]+}}UnarchivedTwice'
|
||||
// CHECK-NEXT: @objc(_TtC{{.+[0-9]+}}UnarchivedTwice)
|
||||
// CHECK-NEXT: @objc(ABCUnarchivedTwice)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkUnarchiving(UnarchivedTwice.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
private class ArchivedThenUnarchived {}
|
||||
|
||||
checkArchiving(ArchivedThenUnarchived.self)
|
||||
// CHECK-NEXT: Attempting to archive Swift class '_Test.(ArchivedThenUnarchived in {{.+}})' with mangled runtime name '_TtC{{.+[0-9]+}}ArchivedThenUnarchived'
|
||||
// CHECK-NEXT: @objc(_TtC{{.+[0-9]+}}ArchivedThenUnarchived)
|
||||
// CHECK-NEXT: @objc(ABCArchivedThenUnarchived)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkUnarchiving(ArchivedThenUnarchived.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
private class UnarchivedThenArchived {}
|
||||
|
||||
checkUnarchiving(UnarchivedThenArchived.self)
|
||||
// CHECK-NEXT: Attempting to unarchive Swift class '_Test.(UnarchivedThenArchived in {{.+}})' with mangled runtime name '_TtC{{.+[0-9]+}}UnarchivedThenArchived'
|
||||
// CHECK-NEXT: @objc(_TtC{{.+[0-9]+}}UnarchivedThenArchived)
|
||||
// CHECK-NEXT: @objc(ABCUnarchivedThenArchived)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkArchiving(UnarchivedThenArchived.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
class Outer {
|
||||
class ArchivedTwice {}
|
||||
class UnarchivedTwice {}
|
||||
class ArchivedThenUnarchived {}
|
||||
class UnarchivedThenArchived {}
|
||||
}
|
||||
|
||||
checkArchiving(Outer.ArchivedTwice.self)
|
||||
// CHECK-NEXT: Attempting to archive Swift class '_Test.Outer.ArchivedTwice'
|
||||
// CHECK-NEXT: @objc(_TtC{{.+[0-9]+}}ArchivedTwice)
|
||||
// CHECK-NEXT: @objc(ABCArchivedTwice)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkArchiving(Outer.ArchivedTwice.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
checkUnarchiving(Outer.UnarchivedTwice.self)
|
||||
// CHECK-NEXT: Attempting to unarchive Swift class '_Test.Outer.UnarchivedTwice'
|
||||
// CHECK-NEXT: @objc(_TtC{{.+[0-9]+}}UnarchivedTwice)
|
||||
// CHECK-NEXT: @objc(ABCUnarchivedTwice)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkUnarchiving(Outer.UnarchivedTwice.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
checkArchiving(Outer.ArchivedThenUnarchived.self)
|
||||
// CHECK-NEXT: Attempting to archive Swift class '_Test.Outer.ArchivedThenUnarchived'
|
||||
// CHECK-NEXT: @objc(_TtC{{.+[0-9]+}}ArchivedThenUnarchived)
|
||||
// CHECK-NEXT: @objc(ABCArchivedThenUnarchived)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkUnarchiving(Outer.ArchivedThenUnarchived.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
checkUnarchiving(Outer.UnarchivedThenArchived.self)
|
||||
// CHECK-NEXT: Attempting to unarchive Swift class '_Test.Outer.UnarchivedThenArchived'
|
||||
// CHECK-NEXT: @objc(_TtC{{.+[0-9]+}}UnarchivedThenArchived)
|
||||
// CHECK-NEXT: @objc(ABCUnarchivedThenArchived)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkArchiving(Outer.UnarchivedThenArchived.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
|
||||
private class 日本語 {}
|
||||
|
||||
checkArchiving(日本語.self)
|
||||
// CHECK-NEXT: Attempting to archive Swift class '_Test.(日本語 in {{.+}})'
|
||||
// CHECK-NEXT: @objc(_TtC{{.+[0-9]+}}9日本語)
|
||||
// CHECK-NEXT: @objc(ABCMyModel)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkArchiving(日本語.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
|
||||
class ArchivedTwiceGeneric<T> {}
|
||||
|
||||
checkArchiving(ArchivedTwiceGeneric<Int>.self)
|
||||
// CHECK-NEXT: Attempting to archive generic Swift class '_Test.ArchivedTwiceGeneric<Swift.Int>' with mangled runtime name '_TtGC5_Test20ArchivedTwiceGenericSi_'
|
||||
// CHECK-NEXT: NSKeyedUnarchiver.setClass(_:forClassName:)
|
||||
// CHECK-SAME: _TtGC5_Test20ArchivedTwiceGenericSi_
|
||||
// CHECK-NEXT: NSKeyedArchiver.setClassName(_:for:)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkArchiving(ArchivedTwiceGeneric<Int>.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
checkArchiving(ArchivedTwiceGeneric<NSObject>.self)
|
||||
// CHECK-NEXT: Attempting to archive generic Swift class '_Test.ArchivedTwiceGeneric<__ObjC.NSObject>' with mangled runtime name '_TtGC5_Test20ArchivedTwiceGenericCSo8NSObject_'
|
||||
// CHECK-NEXT: NSKeyedUnarchiver.setClass(_:forClassName:)
|
||||
// CHECK-SAME: _TtGC5_Test20ArchivedTwiceGenericCSo8NSObject_
|
||||
// CHECK-NEXT: NSKeyedArchiver.setClassName(_:for:)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkArchiving(ArchivedTwiceGeneric<NSObject>.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
class UnarchivedTwiceGeneric<T> {}
|
||||
|
||||
checkUnarchiving(UnarchivedTwiceGeneric<Int>.self)
|
||||
// CHECK-NEXT: Attempting to unarchive generic Swift class '_Test.UnarchivedTwiceGeneric<Swift.Int>' with mangled runtime name '_TtGC5_Test22UnarchivedTwiceGenericSi_'
|
||||
// CHECK-NEXT: NSKeyedUnarchiver.setClass(_:forClassName:)
|
||||
// CHECK-SAME: _TtGC5_Test22UnarchivedTwiceGenericSi_
|
||||
// CHECK-NEXT: NSKeyedArchiver.setClassName(_:for:)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkUnarchiving(UnarchivedTwiceGeneric<Int>.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
class ArchivedThenUnarchivedGeneric<T> {}
|
||||
|
||||
checkArchiving(ArchivedThenUnarchivedGeneric<Int>.self)
|
||||
// CHECK-NEXT: Attempting to archive generic Swift class '_Test.ArchivedThenUnarchivedGeneric<Swift.Int>' with mangled runtime name '_TtGC5_Test29ArchivedThenUnarchivedGenericSi_'
|
||||
// CHECK-NEXT: NSKeyedUnarchiver.setClass(_:forClassName:)
|
||||
// CHECK-SAME: _TtGC5_Test29ArchivedThenUnarchivedGenericSi_
|
||||
// CHECK-NEXT: NSKeyedArchiver.setClassName(_:for:)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkUnarchiving(ArchivedThenUnarchivedGeneric<Int>.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
|
||||
class UnarchivedThenArchivedGeneric<T> {}
|
||||
|
||||
checkUnarchiving(UnarchivedThenArchivedGeneric<Int>.self)
|
||||
// CHECK-NEXT: Attempting to unarchive generic Swift class '_Test.UnarchivedThenArchivedGeneric<Swift.Int>' with mangled runtime name '_TtGC5_Test29UnarchivedThenArchivedGenericSi_'
|
||||
// CHECK-NEXT: NSKeyedUnarchiver.setClass(_:forClassName:)
|
||||
// CHECK-SAME: _TtGC5_Test29UnarchivedThenArchivedGenericSi_
|
||||
// CHECK-NEXT: NSKeyedArchiver.setClassName(_:for:)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkArchiving(UnarchivedThenArchivedGeneric<Int>.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
Reference in New Issue
Block a user