mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
clang is miscompiling some swiftcall functions on armv7s.
Stop using swiftcall in some places until it is fixed.
Reverts c5bf2ec (#13299).
rdar://35973477
292 lines
10 KiB
Plaintext
292 lines
10 KiB
Plaintext
#import <Foundation/Foundation.h>
|
|
|
|
#include <objc/runtime.h>
|
|
|
|
#include "swift/Runtime/HeapObject.h"
|
|
#include "swift/Runtime/Metadata.h"
|
|
|
|
@interface NSKeyedUnarchiver (SwiftAdditions)
|
|
+ (int)_swift_checkClassAndWarnForKeyedArchiving:(Class)cls
|
|
operation:(int)operation
|
|
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;
|
|
}
|
|
|
|
template <typename T, size_t N>
|
|
static constexpr size_t arrayLength(T (&)[N]) { return N; }
|
|
|
|
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>::Return rawValue)
|
|
: data(swift::TwoWordPair<const char *, uintptr_t>(rawValue).first),
|
|
length(swift::TwoWordPair<const char *, uintptr_t>(rawValue).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 objcClass is good for archiving.
|
|
///
|
|
/// If not, a runtime warning is printed.
|
|
///
|
|
/// \param operation Specifies the archiving operation. Valid operations are:
|
|
/// 0: archiving
|
|
/// 1: unarchiving
|
|
/// \return Returns the status
|
|
/// 0: not a problem class (either non-Swift or has an explicit name)
|
|
/// 1: a Swift generic class
|
|
/// 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)objcClass
|
|
operation:(int)operation {
|
|
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() & ClassFlags::HasCustomObjCName)
|
|
return 0;
|
|
|
|
// Is it a mangled name?
|
|
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
|
|
// start with "_T".
|
|
if (strchr(className, '.'))
|
|
return 0;
|
|
|
|
// Is it a generic class?
|
|
if (theClass->getDescription()->GenericParams.isGeneric()) {
|
|
logIfFirstOccurrence(objcClass, ^{
|
|
// Use actual NSStrings to force UTF-8.
|
|
StringRefLite demangledName = swift_getTypeName(theClass,
|
|
/*qualified*/true);
|
|
NSString *demangledString = demangledName.newNSStringNoCopy();
|
|
NSString *mangledString = NSStringFromClass(objcClass);
|
|
|
|
NSString *primaryMessage;
|
|
switch (operation) {
|
|
case 1:
|
|
primaryMessage = [[NSString alloc] initWithFormat:
|
|
@"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:
|
|
primaryMessage = [[NSString alloc] initWithFormat:
|
|
@"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;
|
|
}
|
|
NSString *generatedNote = [[NSString alloc] initWithFormat:
|
|
@"To avoid this failure, create a concrete subclass and register "
|
|
"it with NSKeyedUnarchiver.setClass(_:forClassName:) instead, "
|
|
"using the name \"%@\".", mangledString];
|
|
const char *staticNote =
|
|
"If you need to produce archives compatible with older versions "
|
|
"of your program, use NSKeyedArchiver.setClassName(_:for:) as well.";
|
|
|
|
NSLog(@"%@", primaryMessage);
|
|
NSLog(@"%@", generatedNote);
|
|
NSLog(@"%s", staticNote);
|
|
|
|
RuntimeErrorDetails::Note notes[] = {
|
|
{ [generatedNote UTF8String], /*numFixIts*/0, /*fixIts*/nullptr },
|
|
{ staticNote, /*numFixIts*/0, /*fixIts*/nullptr },
|
|
};
|
|
|
|
RuntimeErrorDetails errorInfo = {};
|
|
errorInfo.version = RuntimeErrorDetails::currentVersion;
|
|
errorInfo.errorType = "nskeyedarchiver-incompatible-class";
|
|
errorInfo.notes = notes;
|
|
errorInfo.numNotes = arrayLength(notes);
|
|
|
|
_swift_reportToDebugger(RuntimeErrorFlagNone, [primaryMessage UTF8String],
|
|
&errorInfo);
|
|
|
|
[primaryMessage release];
|
|
[generatedNote release];
|
|
[demangledString release];
|
|
});
|
|
return 1;
|
|
}
|
|
|
|
// It's a swift class with a (compiler generated) mangled name, which should
|
|
// be written into an NSArchive.
|
|
logIfFirstOccurrence(objcClass, ^{
|
|
// Use actual NSStrings to force UTF-8.
|
|
StringRefLite demangledName = swift_getTypeName(theClass,/*qualified*/true);
|
|
NSString *demangledString = demangledName.newNSStringNoCopy();
|
|
NSString *mangledString = NSStringFromClass(objcClass);
|
|
|
|
NSString *primaryMessage;
|
|
switch (operation) {
|
|
case 1:
|
|
primaryMessage = [[NSString alloc] initWithFormat:
|
|
@"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:
|
|
primaryMessage = [[NSString alloc] initWithFormat:
|
|
@"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;
|
|
}
|
|
|
|
NSString *firstNote = [[NSString alloc] initWithFormat:
|
|
@"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";
|
|
}
|
|
|
|
NSString *secondNote = [[NSString alloc] initWithFormat:
|
|
@"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];
|
|
|
|
NSLog(@"%@", primaryMessage);
|
|
NSLog(@"%@", firstNote);
|
|
NSLog(@"%@", secondNote);
|
|
|
|
// FIXME: We could suggest these as fix-its if we had source locations for
|
|
// the class.
|
|
RuntimeErrorDetails::Note notes[] = {
|
|
{ [firstNote UTF8String], /*numFixIts*/0, /*fixIts*/nullptr },
|
|
{ [secondNote UTF8String], /*numFixIts*/0, /*fixIts*/nullptr },
|
|
};
|
|
|
|
RuntimeErrorDetails errorInfo = {};
|
|
errorInfo.version = RuntimeErrorDetails::currentVersion;
|
|
errorInfo.errorType = "nskeyedarchiver-incompatible-class";
|
|
errorInfo.notes = notes;
|
|
errorInfo.numNotes = arrayLength(notes);
|
|
|
|
_swift_reportToDebugger(RuntimeErrorFlagNone, [primaryMessage UTF8String],
|
|
&errorInfo);
|
|
|
|
[primaryMessage release];
|
|
[firstNote release];
|
|
[secondNote release];
|
|
[demangledString release];
|
|
});
|
|
return 2;
|
|
}
|
|
@end
|