2
2
3
3
#include < objc/runtime.h>
4
4
5
+ #include " swift/Runtime/HeapObject.h"
5
6
#include " swift/Runtime/Metadata.h"
6
7
7
8
@interface NSKeyedUnarchiver (SwiftAdditions)
@@ -10,9 +11,116 @@ + (int)_swift_checkClassAndWarnForKeyedArchiving:(Class)cls
10
11
NS_SWIFT_NAME(_swift_checkClassAndWarnForKeyedArchiving(_:operation:));
11
12
@end
12
13
14
+ static bool isASCIIIdentifierChar (char c) {
15
+ if (c >= ' a' && c <= ' z' ) return true ;
16
+ if (c >= ' A' && c <= ' Z' ) return true ;
17
+ if (c >= ' 0' && c <= ' 9' ) return true ;
18
+ if (c == ' _' ) return true ;
19
+ if (c == ' $' ) return true ;
20
+ return false ;
21
+ }
22
+
23
+ static void logIfFirstOccurrence (Class objcClass, void (^log)(void )) {
24
+ static auto queue = dispatch_queue_create (
25
+ " SwiftFoundation._checkClassAndWarnForKeyedArchivingQueue" ,
26
+ DISPATCH_QUEUE_SERIAL);
27
+ static NSHashTable *seenClasses = nil ;
28
+
29
+ dispatch_sync (queue, ^{
30
+ // Will be NO when seenClasses is still nil.
31
+ if ([seenClasses containsObject: objcClass])
32
+ return ;
33
+
34
+ if (!seenClasses) {
35
+ NSPointerFunctionsOptions options = 0 ;
36
+ options |= NSPointerFunctionsOpaqueMemory;
37
+ options |= NSPointerFunctionsObjectPointerPersonality;
38
+ seenClasses = [[NSHashTable alloc ] initWithOptions: options capacity: 16 ];
39
+ }
40
+ [seenClasses addObject: objcClass];
41
+
42
+ // Synchronize logging so that multiple lines aren't interleaved.
43
+ log ();
44
+ });
45
+ }
46
+
47
+ namespace {
48
+ class StringRefLite {
49
+ StringRefLite (const char *data, size_t len) : data(data), length(len) {}
50
+ public:
51
+ const char *data;
52
+ size_t length;
53
+
54
+ StringRefLite () : data(nullptr ), length(0 ) {}
55
+
56
+ template <size_t N>
57
+ StringRefLite (const char (&staticStr)[N]) : data(staticStr), length(N) {}
58
+
59
+ StringRefLite (swift::TwoWordPair<const char *, uintptr_t > pair)
60
+ : data(pair.first), length(pair.second) {}
61
+
62
+ NS_RETURNS_RETAINED
63
+ NSString *newNSStringNoCopy () const {
64
+ return [[NSString alloc ] initWithBytesNoCopy: const_cast <char *>(data)
65
+ length: length
66
+ encoding: NSUTF8StringEncoding
67
+ freeWhenDone: NO ];
68
+ }
69
+
70
+ const char &operator [](size_t offset) const {
71
+ assert (offset < length);
72
+ return data[offset];
73
+ }
74
+
75
+ StringRefLite slice (size_t from, size_t to) const {
76
+ assert (from <= to);
77
+ assert (to <= length);
78
+ return {data + from, to - from};
79
+ }
80
+
81
+ const char *begin () const {
82
+ return data;
83
+ }
84
+ const char *end () const {
85
+ return data + length;
86
+ }
87
+ };
88
+ }
89
+
90
+ // / Assume that a non-generic demangled class name always ends in ".MyClass"
91
+ // / or ".(MyClass plus extra info)".
92
+ static StringRefLite findBaseName (StringRefLite demangledName) {
93
+ size_t end = demangledName.length ;
94
+ size_t parenCount = 0 ;
95
+ for (size_t i = end; i != 0 ; --i) {
96
+ switch (demangledName[i - 1 ]) {
97
+ case ' .' :
98
+ if (parenCount == 0 ) {
99
+ if (i != end && demangledName[i] == ' (' )
100
+ ++i;
101
+ return demangledName.slice (i, end);
102
+ }
103
+ break ;
104
+ case ' )' :
105
+ parenCount += 1 ;
106
+ break ;
107
+ case ' (' :
108
+ if (parenCount > 0 )
109
+ parenCount -= 1 ;
110
+ break ;
111
+ case ' ' :
112
+ end = i - 1 ;
113
+ break ;
114
+ default :
115
+ break ;
116
+ }
117
+ }
118
+ return {};
119
+ }
120
+
13
121
@implementation NSKeyedUnarchiver (SwiftAdditions)
14
122
15
- // / Checks if class \p cls is good for archiving.
123
+ // / Checks if class \p objcClass is good for archiving.
16
124
// /
17
125
// / If not, a runtime warning is printed.
18
126
// /
@@ -25,20 +133,21 @@ @implementation NSKeyedUnarchiver (SwiftAdditions)
25
133
// / 2: a Swift non-generic class where adding @objc is valid
26
134
// / Future versions of this API will return nonzero values for additional cases
27
135
// / that mean the class shouldn't be archived.
28
- + (int )_swift_checkClassAndWarnForKeyedArchiving : (Class )cls
136
+ + (int )_swift_checkClassAndWarnForKeyedArchiving : (Class )objcClass
29
137
operation : (int )operation {
30
- const swift::ClassMetadata *theClass = (swift::ClassMetadata *)cls;
138
+ using namespace swift ;
139
+ const ClassMetadata *theClass = (ClassMetadata *)objcClass;
31
140
32
141
// Is it a (real) swift class?
33
142
if (!theClass->isTypeMetadata () || theClass->isArtificialSubclass ())
34
143
return 0 ;
35
144
36
145
// Does the class already have a custom name?
37
- if (theClass->getFlags () & swift:: ClassFlags::HasCustomObjCName)
146
+ if (theClass->getFlags () & ClassFlags::HasCustomObjCName)
38
147
return 0 ;
39
148
40
149
// Is it a mangled name?
41
- const char *className = class_getName (cls );
150
+ const char *className = class_getName (objcClass );
42
151
if (!(className[0 ] == ' _' && className[1 ] == ' T' ))
43
152
return 0 ;
44
153
// Is it a name in the form <module>.<class>? Note: the module name could
@@ -48,13 +157,74 @@ + (int)_swift_checkClassAndWarnForKeyedArchiving:(Class)cls
48
157
49
158
// Is it a generic class?
50
159
if (theClass->getDescription ()->GenericParams .isGeneric ()) {
51
- // TODO: print a warning
160
+ logIfFirstOccurrence (objcClass, ^{
161
+ // Use actual NSStrings to force UTF-8.
162
+ StringRefLite demangledName = swift_getTypeName (theClass,
163
+ /* qualified*/ true );
164
+ NSString *demangledString = demangledName.newNSStringNoCopy ();
165
+ NSString *mangledString = NSStringFromClass (objcClass);
166
+ switch (operation) {
167
+ case 1 :
168
+ NSLog (@" Attempting to unarchive generic Swift class '%@ ' with mangled "
169
+ " runtime name '%@ '. Runtime names for generic classes are "
170
+ " unstable and may change in the future, leading to "
171
+ " non-decodable data." , demangledString, mangledString);
172
+ break ;
173
+ default :
174
+ NSLog (@" Attempting to archive generic Swift class '%@ ' with mangled "
175
+ " runtime name '%@ '. Runtime names for generic classes are "
176
+ " unstable and may change in the future, leading to "
177
+ " non-decodable data." , demangledString, mangledString);
178
+ break ;
179
+ }
180
+ NSLog (@" To avoid this failure, create a concrete subclass and register "
181
+ " it with NSKeyedUnarchiver.setClass(_:forClassName:) instead, "
182
+ " using the name \" %@ \" ." , mangledString);
183
+ NSLog (@" If you need to produce archives compatible with older versions "
184
+ " of your program, use NSKeyedArchiver.setClassName(_:for:) "
185
+ " as well." );
186
+ [demangledString release ];
187
+ });
52
188
return 1 ;
53
189
}
54
190
55
191
// It's a swift class with a (compiler generated) mangled name, which should
56
192
// be written into an NSArchive.
57
- // TODO: print a warning
193
+ logIfFirstOccurrence (objcClass, ^{
194
+ // Use actual NSStrings to force UTF-8.
195
+ StringRefLite demangledName = swift_getTypeName (theClass,/* qualified*/ true );
196
+ NSString *demangledString = demangledName.newNSStringNoCopy ();
197
+ NSString *mangledString = NSStringFromClass (objcClass);
198
+ switch (operation) {
199
+ case 1 :
200
+ NSLog (@" Attempting to unarchive Swift class '%@ ' with mangled runtime "
201
+ " name '%@ '. The runtime name for this class is unstable and may "
202
+ " change in the future, leading to non-decodable data." ,
203
+ demangledString, mangledString);
204
+ break ;
205
+ default :
206
+ NSLog (@" Attempting to archive Swift class '%@ ' with mangled runtime "
207
+ " name '%@ '. The runtime name for this class is unstable and may "
208
+ " change in the future, leading to non-decodable data." ,
209
+ demangledString, mangledString);
210
+ break ;
211
+ }
212
+ [demangledString release ];
213
+ NSLog (@" You can use the 'objc' attribute to ensure that the name will not "
214
+ " change: \" @objc(%@ )\" " , mangledString);
215
+
216
+ StringRefLite baseName = findBaseName (demangledName);
217
+ // Offer a more generic message if the base name we found doesn't look like
218
+ // an ASCII identifier. This avoids printing names like "ABCモデル".
219
+ if (baseName.length == 0 ||
220
+ !std::all_of (baseName.begin (), baseName.end (), isASCIIIdentifierChar)) {
221
+ baseName = " MyModel" ;
222
+ }
223
+
224
+ NSLog (@" If there are no existing archives containing this class, you "
225
+ " should choose a unique, prefixed name instead: "
226
+ " \" @objc(ABC%1$.*2$s )\" " , baseName.data, (int )baseName.length);
227
+ });
58
228
return 2 ;
59
229
}
60
230
@end
0 commit comments