Skip to content

Commit fd3b585

Browse files
committed
[stdlib] Surface NSKeyedArchiver issues in Xcode.
By calling through to swift_reportToDebugger, Xcode can pick up the NSKeyedArchiver/Unarchiver issues with Swift classes and display them in the Issues Navigator. This increases the probability that they'll be seen and acted upon. This is not a fully-general interface yet, please do not start hooking random things up to it. Especially if you're working on something that doesn't ship with Xcode itself. :-) rdar://problem/32900735
1 parent 9c10398 commit fd3b585

File tree

1 file changed

+88
-28
lines changed

1 file changed

+88
-28
lines changed

stdlib/public/SDK/Foundation/CheckClass.mm

Lines changed: 88 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ static bool isASCIIIdentifierChar(char c) {
2020
return false;
2121
}
2222

23+
template <typename T, size_t N>
24+
static constexpr size_t arrayLength(T (&)[N]) { return N; }
25+
2326
static void logIfFirstOccurrence(Class objcClass, void (^log)(void)) {
2427
static auto queue = dispatch_queue_create(
2528
"SwiftFoundation._checkClassAndWarnForKeyedArchivingQueue",
@@ -164,26 +167,52 @@ + (int)_swift_checkClassAndWarnForKeyedArchiving:(Class)objcClass
164167
/*qualified*/true);
165168
NSString *demangledString = demangledName.newNSStringNoCopy();
166169
NSString *mangledString = NSStringFromClass(objcClass);
170+
171+
NSString *primaryMessage;
167172
switch (operation) {
168173
case 1:
169-
NSLog(@"Attempting to unarchive generic Swift class '%@' with mangled "
170-
"runtime name '%@'. Runtime names for generic classes are "
171-
"unstable and may change in the future, leading to "
172-
"non-decodable data.", demangledString, mangledString);
174+
primaryMessage = [[NSString alloc] initWithFormat:
175+
@"Attempting to unarchive generic Swift class '%@' with mangled "
176+
"runtime name '%@'. Runtime names for generic classes are "
177+
"unstable and may change in the future, leading to "
178+
"non-decodable data.", demangledString, mangledString];
173179
break;
174180
default:
175-
NSLog(@"Attempting to archive generic Swift class '%@' with mangled "
176-
"runtime name '%@'. Runtime names for generic classes are "
177-
"unstable and may change in the future, leading to "
178-
"non-decodable data.", demangledString, mangledString);
181+
primaryMessage = [[NSString alloc] initWithFormat:
182+
@"Attempting to archive generic Swift class '%@' with mangled "
183+
"runtime name '%@'. Runtime names for generic classes are "
184+
"unstable and may change in the future, leading to "
185+
"non-decodable data.", demangledString, mangledString];
179186
break;
180187
}
181-
NSLog(@"To avoid this failure, create a concrete subclass and register "
182-
"it with NSKeyedUnarchiver.setClass(_:forClassName:) instead, "
183-
"using the name \"%@\".", mangledString);
184-
NSLog(@"If you need to produce archives compatible with older versions "
185-
"of your program, use NSKeyedArchiver.setClassName(_:for:) "
186-
"as well.");
188+
NSString *generatedNote = [[NSString alloc] initWithFormat:
189+
@"To avoid this failure, create a concrete subclass and register "
190+
"it with NSKeyedUnarchiver.setClass(_:forClassName:) instead, "
191+
"using the name \"%@\".", mangledString];
192+
const char *staticNote =
193+
"If you need to produce archives compatible with older versions "
194+
"of your program, use NSKeyedArchiver.setClassName(_:for:) as well.";
195+
196+
NSLog(@"%@", primaryMessage);
197+
NSLog(@"%@", generatedNote);
198+
NSLog(@"%s", staticNote);
199+
200+
RuntimeErrorDetails::Note notes[] = {
201+
{ [generatedNote UTF8String], /*numFixIts*/0, /*fixIts*/nullptr },
202+
{ staticNote, /*numFixIts*/0, /*fixIts*/nullptr },
203+
};
204+
205+
RuntimeErrorDetails errorInfo = {};
206+
errorInfo.version = RuntimeErrorDetails::currentVersion;
207+
errorInfo.errorType = "nskeyedarchiver-incompatible-class";
208+
errorInfo.notes = notes;
209+
errorInfo.numNotes = arrayLength(notes);
210+
211+
_swift_reportToDebugger(RuntimeErrorFlagNone, [primaryMessage UTF8String],
212+
&errorInfo);
213+
214+
[primaryMessage release];
215+
[generatedNote release];
187216
[demangledString release];
188217
});
189218
return 1;
@@ -196,23 +225,28 @@ + (int)_swift_checkClassAndWarnForKeyedArchiving:(Class)objcClass
196225
StringRefLite demangledName = swift_getTypeName(theClass,/*qualified*/true);
197226
NSString *demangledString = demangledName.newNSStringNoCopy();
198227
NSString *mangledString = NSStringFromClass(objcClass);
228+
229+
NSString *primaryMessage;
199230
switch (operation) {
200231
case 1:
201-
NSLog(@"Attempting to unarchive Swift class '%@' with mangled runtime "
202-
"name '%@'. The runtime name for this class is unstable and may "
203-
"change in the future, leading to non-decodable data.",
204-
demangledString, mangledString);
232+
primaryMessage = [[NSString alloc] initWithFormat:
233+
@"Attempting to unarchive Swift class '%@' with mangled runtime "
234+
"name '%@'. The runtime name for this class is unstable and may "
235+
"change in the future, leading to non-decodable data.",
236+
demangledString, mangledString];
205237
break;
206238
default:
207-
NSLog(@"Attempting to archive Swift class '%@' with mangled runtime "
208-
"name '%@'. The runtime name for this class is unstable and may "
209-
"change in the future, leading to non-decodable data.",
210-
demangledString, mangledString);
239+
primaryMessage = [[NSString alloc] initWithFormat:
240+
@"Attempting to archive Swift class '%@' with mangled runtime "
241+
"name '%@'. The runtime name for this class is unstable and may "
242+
"change in the future, leading to non-decodable data.",
243+
demangledString, mangledString];
211244
break;
212245
}
213-
[demangledString release];
214-
NSLog(@"You can use the 'objc' attribute to ensure that the name will not "
215-
"change: \"@objc(%@)\"", mangledString);
246+
247+
NSString *firstNote = [[NSString alloc] initWithFormat:
248+
@"You can use the 'objc' attribute to ensure that the name will not "
249+
"change: \"@objc(%@)\"", mangledString];
216250

217251
StringRefLite baseName = findBaseName(demangledName);
218252
// Offer a more generic message if the base name we found doesn't look like
@@ -222,9 +256,35 @@ + (int)_swift_checkClassAndWarnForKeyedArchiving:(Class)objcClass
222256
baseName = "MyModel";
223257
}
224258

225-
NSLog(@"If there are no existing archives containing this class, you "
226-
"should choose a unique, prefixed name instead: "
227-
"\"@objc(ABC%1$.*2$s)\"", baseName.data, (int)baseName.length);
259+
NSString *secondNote = [[NSString alloc] initWithFormat:
260+
@"If there are no existing archives containing this class, you should "
261+
"choose a unique, prefixed name instead: \"@objc(ABC%1$.*2$s)\"",
262+
baseName.data, (int)baseName.length];
263+
264+
NSLog(@"%@", primaryMessage);
265+
NSLog(@"%@", firstNote);
266+
NSLog(@"%@", secondNote);
267+
268+
// FIXME: We could suggest these as fix-its if we had source locations for
269+
// the class.
270+
RuntimeErrorDetails::Note notes[] = {
271+
{ [firstNote UTF8String], /*numFixIts*/0, /*fixIts*/nullptr },
272+
{ [secondNote UTF8String], /*numFixIts*/0, /*fixIts*/nullptr },
273+
};
274+
275+
RuntimeErrorDetails errorInfo = {};
276+
errorInfo.version = RuntimeErrorDetails::currentVersion;
277+
errorInfo.errorType = "nskeyedarchiver-incompatible-class";
278+
errorInfo.notes = notes;
279+
errorInfo.numNotes = arrayLength(notes);
280+
281+
_swift_reportToDebugger(RuntimeErrorFlagNone, [primaryMessage UTF8String],
282+
&errorInfo);
283+
284+
[primaryMessage release];
285+
[firstNote release];
286+
[secondNote release];
287+
[demangledString release];
228288
});
229289
return 2;
230290
}

0 commit comments

Comments
 (0)