mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[Foundation] Update & simplify class name stability check
Move the ObjC class name stability check logic to the Swift runtime, exposing it as a new SPI called _swift_isObjCTypeNameSerializable. Update the reporting logic. The ObjC names of generic classes are considered stable now, but private classes and classes defined in function bodies or other anonymous contexts are unstable by design. On the overlay side, rewrite the check’s implementation in Swift and considerably simplify it. rdar://57809977
This commit is contained in:
40
include/swift/Runtime/FoundationSupport.h
Normal file
40
include/swift/Runtime/FoundationSupport.h
Normal file
@@ -0,0 +1,40 @@
|
||||
//===--- FoundationSupport.cpp - Support functions for Foundation ---------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Helper functions for the Foundation framework.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef SWIFT_RUNTIME_FOUNDATION_SUPPORT_H
|
||||
#define SWIFT_RUNTIME_FOUNDATION_SUPPORT_H
|
||||
|
||||
#include "swift/Runtime/Config.h"
|
||||
|
||||
#if SWIFT_OBJC_INTEROP
|
||||
#include <objc/runtime.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace swift { extern "C" {
|
||||
#endif
|
||||
|
||||
/// Returns a boolean indicating whether the Objective-C name of a class type is
|
||||
/// stable across executions, i.e., if the class name is safe to serialize. (The
|
||||
/// names of private and local types are unstable.)
|
||||
SWIFT_RUNTIME_STDLIB_SPI
|
||||
bool _swift_isObjCTypeNameSerializable(Class theClass);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}} // extern "C", namespace swift
|
||||
#endif
|
||||
|
||||
#endif // SWIFT_OBJC_INTEROP
|
||||
#endif // SWIFT_RUNTIME_FOUNDATION_SUPPORT_H
|
||||
@@ -7,7 +7,7 @@ add_swift_target_library(swiftFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES
|
||||
BundleLookup.mm
|
||||
Calendar.swift
|
||||
CharacterSet.swift
|
||||
CheckClass.mm
|
||||
CheckClass.swift
|
||||
Codable.swift
|
||||
Collections+DataProtocol.swift
|
||||
CombineTypealiases.swift
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
#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::TypeNamePair rawValue)
|
||||
: data(rawValue.data),
|
||||
length(rawValue.length){}
|
||||
|
||||
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()->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
|
||||
64
stdlib/public/Darwin/Foundation/CheckClass.swift
Normal file
64
stdlib/public/Darwin/Foundation/CheckClass.swift
Normal file
@@ -0,0 +1,64 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@_exported import Foundation // Clang module
|
||||
@_implementationOnly import _SwiftFoundationOverlayShims
|
||||
import Dispatch
|
||||
|
||||
private let _queue = DispatchQueue(label: "com.apple.SwiftFoundation._checkClassAndWarnForKeyedArchivingQueue")
|
||||
private var _seenClasses: Set<ObjectIdentifier> = []
|
||||
private func _isClassFirstSeen(_ theClass: AnyClass) -> Bool {
|
||||
_queue.sync {
|
||||
let id = ObjectIdentifier(theClass)
|
||||
return _seenClasses.insert(id).inserted
|
||||
}
|
||||
}
|
||||
|
||||
extension NSKeyedUnarchiver {
|
||||
/// Checks if class `theClass` is good for archiving.
|
||||
///
|
||||
/// If not, a runtime warning is printed.
|
||||
///
|
||||
/// - Parameter operation: Specifies the archiving operation. Supported values
|
||||
/// are 0 for archiving, and 1 for unarchiving.
|
||||
/// - Returns: 0 if the given class is safe to archive, and non-zero if it
|
||||
/// isn't.
|
||||
@objc(_swift_checkClassAndWarnForKeyedArchiving:operation:)
|
||||
@usableFromInline
|
||||
internal class func _swift_checkClassAndWarnForKeyedArchiving(
|
||||
_ theClass: AnyClass,
|
||||
operation: CInt
|
||||
) -> CInt {
|
||||
if _swift_isObjCTypeNameSerializable(theClass) { return 0 }
|
||||
|
||||
if _isClassFirstSeen(theClass) {
|
||||
let demangledName = String(reflecting: theClass)
|
||||
let mangledName = NSStringFromClass(theClass)
|
||||
|
||||
let op = (operation == 1 ? "unarchive" : "archive")
|
||||
|
||||
let message = """
|
||||
Attempting to \(op) Swift class '\(demangledName)' with unstable runtime name '\(mangledName)'.
|
||||
The runtime name for this class may change in the future, leading to non-decodable data.
|
||||
|
||||
You can use the 'objc' attribute to ensure that the name will not change:
|
||||
"@objc(\(mangledName))"
|
||||
|
||||
If there are no existing archives containing this class, you should choose a unique, prefixed name instead:
|
||||
"@objc(ABCMyModel)"
|
||||
"""
|
||||
NSLog("%@", message)
|
||||
_swift_reportToDebugger(0, message, nil)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
@@ -75,3 +75,8 @@ static inline _Bool _withStackOrHeapBuffer(size_t amount, void (__attribute__((n
|
||||
@protocol _NSKVOCompatibilityShim <NSObject>
|
||||
+ (void)_noteProcessHasUsedKVOSwiftOverlay;
|
||||
@end
|
||||
|
||||
|
||||
// Exported by libswiftCore:
|
||||
extern bool _swift_isObjCTypeNameSerializable(Class theClass);
|
||||
extern void _swift_reportToDebugger(uintptr_t flags, const char *message, void *details);
|
||||
|
||||
@@ -43,6 +43,7 @@ set(swift_runtime_sources
|
||||
Exclusivity.cpp
|
||||
ExistentialContainer.cpp
|
||||
Float16Support.cpp
|
||||
FoundationSupport.cpp
|
||||
Heap.cpp
|
||||
HeapObject.cpp
|
||||
ImageInspectionCommon.cpp
|
||||
|
||||
61
stdlib/public/runtime/FoundationSupport.cpp
Normal file
61
stdlib/public/runtime/FoundationSupport.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
//===--- FoundationSupport.cpp - Support functions for Foundation ---------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Helper functions for the Foundation framework.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "swift/Runtime/FoundationSupport.h"
|
||||
|
||||
#if SWIFT_OBJC_INTEROP
|
||||
#include "swift/Runtime/Metadata.h"
|
||||
#include "swift/Runtime/HeapObject.h"
|
||||
|
||||
using namespace swift;
|
||||
|
||||
/// Returns a boolean indicating whether the Objective-C name of a class type is
|
||||
/// stable across executions, i.e., if the class name is safe to serialize. (The
|
||||
/// names of private and local types are unstable.)
|
||||
bool
|
||||
swift::_swift_isObjCTypeNameSerializable(Class theClass) {
|
||||
auto type = (AnyClassMetadata *)theClass;
|
||||
switch (type->getKind()) {
|
||||
case MetadataKind::ObjCClassWrapper:
|
||||
case MetadataKind::ForeignClass:
|
||||
return true;
|
||||
case MetadataKind::Class: {
|
||||
// Pure ObjC classes always have stable names.
|
||||
if (type->isPureObjC())
|
||||
return true;
|
||||
auto cls = static_cast<const ClassMetadata *>(type);
|
||||
// Peek through artificial subclasses.
|
||||
if (cls->isArtificialSubclass()) {
|
||||
cls = cls->Superclass;
|
||||
}
|
||||
// A custom ObjC name is always considered stable.
|
||||
if (cls->getFlags() & ClassFlags::HasCustomObjCName)
|
||||
return true;
|
||||
// Otherwise the name is stable if the class has no anonymous ancestor context.
|
||||
auto desc = static_cast<const ContextDescriptor *>(cls->getDescription());
|
||||
while (desc) {
|
||||
if (desc->getKind() == ContextDescriptorKind::Anonymous) {
|
||||
return false;
|
||||
}
|
||||
desc = desc->Parent.get();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif // SWIFT_OBJC_INTEROP
|
||||
@@ -1,12 +1,13 @@
|
||||
// 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-codesign %t/a.out
|
||||
// RUN: %target-run %t/a.out | %FileCheck %s
|
||||
// RUN: %target-run %t/a.out
|
||||
|
||||
// REQUIRES: executable_test
|
||||
// REQUIRES: objc_interop
|
||||
|
||||
import Foundation
|
||||
import StdlibUnittest
|
||||
|
||||
class SwiftClass {}
|
||||
|
||||
@@ -35,36 +36,57 @@ struct DEF<T> {
|
||||
class InnerClass : NSObject {}
|
||||
}
|
||||
|
||||
let suite = TestSuite("check_class_for_archiving")
|
||||
defer { runAllTests() }
|
||||
|
||||
let op: Int32 = 0 // archiving
|
||||
|
||||
// CHECK: SwiftClass: 0
|
||||
print("SwiftClass: \(NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(SwiftClass.self, operation: op))")
|
||||
// CHECK: ObjcClass: 0
|
||||
print("ObjcClass: \(NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(ObjcClass.self, operation: op))")
|
||||
// CHECK: NamedClass1: 0
|
||||
print("NamedClass1: \(NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(NamedClass1.self, operation: op))")
|
||||
// CHECK: NamedClass2: 0
|
||||
print("NamedClass2: \(NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(NamedClass2.self, operation: op))")
|
||||
// CHECK: DerivedClass: 0
|
||||
print("DerivedClass: \(NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(DerivedClass.self, operation: op))")
|
||||
// CHECK: DerivedClassWithName: 0
|
||||
print("DerivedClassWithName: \(NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(DerivedClass.self, operation: op))")
|
||||
// CHECK: NSKeyedUnarchiver: 0
|
||||
print("NSKeyedUnarchiver: \(NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(NSKeyedUnarchiver.self, operation: op))")
|
||||
|
||||
if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) {
|
||||
// CHECK: PrivateClass: 2
|
||||
print("PrivateClass: \(NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(PrivateClass.self, operation: op))")
|
||||
// CHECK: GenericClass: 1
|
||||
print("GenericClass: \(NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(GenericClass<Int>.self, operation: op))")
|
||||
// CHECK: InnerClass: 2
|
||||
print("InnerClass: \(NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(ABC.InnerClass.self, operation: op))")
|
||||
// CHECK: InnerClass2: 1
|
||||
print("InnerClass2: \(NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(DEF<Int>.InnerClass.self, operation: op))")
|
||||
} else {
|
||||
// Disable the checks for older OSes because of rdar://problem/50504765
|
||||
print("PrivateClass: 2")
|
||||
print("GenericClass: 1")
|
||||
print("InnerClass: 2")
|
||||
print("InnerClass2: 1")
|
||||
suite.test("SwiftClass") {
|
||||
expectEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(SwiftClass.self, operation: op))
|
||||
}
|
||||
suite.test("ObjcClass") {
|
||||
expectEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(ObjcClass.self, operation: op))
|
||||
}
|
||||
suite.test("NamedClass1") {
|
||||
expectEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(NamedClass1.self, operation: op))
|
||||
}
|
||||
suite.test("NamedClass2") {
|
||||
expectEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(NamedClass2.self, operation: op))
|
||||
}
|
||||
suite.test("DerivedClass") {
|
||||
expectEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(DerivedClass.self, operation: op))
|
||||
}
|
||||
suite.test("DerivedClassWithName") {
|
||||
expectEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(DerivedClassWithName.self, operation: op))
|
||||
}
|
||||
suite.test("NSKeyedUnarchiver") {
|
||||
expectEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(NSKeyedUnarchiver.self, operation: op))
|
||||
}
|
||||
|
||||
// Disable negative tests on older OSes because of rdar://problem/50504765
|
||||
if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) {
|
||||
suite.test("PrivateClass") {
|
||||
expectNotEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(PrivateClass.self, operation: op))
|
||||
}
|
||||
}
|
||||
|
||||
if #available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) {
|
||||
// Generic classes and nested classes were considered to have unstable names
|
||||
// in earlier releases.
|
||||
suite.test("GenericClass") {
|
||||
expectEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(GenericClass<Int>.self, operation: op))
|
||||
}
|
||||
suite.test("InnerClass") {
|
||||
print(NSStringFromClass(ABC.InnerClass.self))
|
||||
expectEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(ABC.InnerClass.self, operation: op))
|
||||
}
|
||||
suite.test("InnerClass2") {
|
||||
print(NSStringFromClass(DEF<Int>.InnerClass.self))
|
||||
expectEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(DEF<Int>.InnerClass.self, operation: op))
|
||||
}
|
||||
|
||||
suite.test("LocalClass") {
|
||||
class LocalClass: NSObject {}
|
||||
expectNotEqual(0, NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(LocalClass.self, operation: op))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,162 +24,132 @@ if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) {
|
||||
|
||||
class SwiftClass {}
|
||||
|
||||
func checkArchiving(_ cls: AnyObject.Type) {
|
||||
NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(cls, operation: 0)
|
||||
func _check(_ label: String, _ cls: AnyObject.Type, _ op: CInt) {
|
||||
NSLog("--%@ start", label)
|
||||
NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(cls, operation: op)
|
||||
NSLog("--%@ end", label)
|
||||
}
|
||||
func checkUnarchiving(_ cls: AnyObject.Type) {
|
||||
NSKeyedUnarchiver._swift_checkClassAndWarnForKeyedArchiving(cls, operation: 1)
|
||||
func checkArchiving(_ label: String, _ cls: AnyObject.Type) {
|
||||
_check(label, cls, 0)
|
||||
}
|
||||
func checkUnarchiving(_ label: String, _ cls: AnyObject.Type) {
|
||||
_check(label, cls, 1)
|
||||
}
|
||||
|
||||
func mark(line: Int32 = #line) {
|
||||
NSLog("--%d--", line)
|
||||
}
|
||||
|
||||
mark() // CHECK: --[[@LINE]]--
|
||||
checkArchiving(SwiftClass.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
// CHECK-LABEL: --SwiftClass start
|
||||
checkArchiving("SwiftClass", SwiftClass.self)
|
||||
// CHECK-NEXT: --SwiftClass end
|
||||
|
||||
|
||||
private class ArchivedTwice {}
|
||||
|
||||
checkArchiving(ArchivedTwice.self)
|
||||
// CHECK-NEXT: Attempting to archive Swift class '_Test.({{.+}}).ArchivedTwice' 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]]--
|
||||
// CHECK-LABEL: --ArchivedTwice1 start
|
||||
checkArchiving("ArchivedTwice1", ArchivedTwice.self)
|
||||
// CHECK: Attempting to archive Swift class '_Test.({{.+}}).ArchivedTwice' with {{.+}} runtime name '_TtC{{.+[0-9]+}}ArchivedTwice'
|
||||
// CHECK: @objc(_TtC{{.+[0-9]+}}ArchivedTwice)
|
||||
|
||||
// CHECK-LABEL: --ArchivedTwice2 start
|
||||
checkArchiving("ArchivedTwice2", ArchivedTwice.self)
|
||||
// CHECK-NEXT: --ArchivedTwice2 end
|
||||
|
||||
private class UnarchivedTwice {}
|
||||
|
||||
checkUnarchiving(UnarchivedTwice.self)
|
||||
// CHECK-NEXT: Attempting to unarchive Swift class '_Test.({{.+}}).UnarchivedTwice' 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]]--
|
||||
// CHECK-LABEL: --UnarchivedTwice1 start
|
||||
checkUnarchiving("UnarchivedTwice1", UnarchivedTwice.self)
|
||||
// CHECK: Attempting to unarchive Swift class '_Test.({{.+}}).UnarchivedTwice' with {{.+}} runtime name '_TtC{{.+[0-9]+}}UnarchivedTwice'
|
||||
// CHECK: @objc(_TtC{{.+[0-9]+}}UnarchivedTwice)
|
||||
|
||||
// CHECK-LABEL: --UnarchivedTwice2 start
|
||||
checkUnarchiving("UnarchivedTwice2", UnarchivedTwice.self)
|
||||
// CHECK-NEXT: --UnarchivedTwice2 end
|
||||
|
||||
private class ArchivedThenUnarchived {}
|
||||
|
||||
checkArchiving(ArchivedThenUnarchived.self)
|
||||
// CHECK-NEXT: Attempting to archive Swift class '_Test.({{.+}}).ArchivedThenUnarchived' 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]]--
|
||||
// CHECK-LABEL: --ArchivedThenUnarchived1 start
|
||||
checkArchiving("ArchivedThenUnarchived1", ArchivedThenUnarchived.self)
|
||||
// CHECK: Attempting to archive Swift class '_Test.({{.+}}).ArchivedThenUnarchived' with {{.+}} runtime name '_TtC{{.+[0-9]+}}ArchivedThenUnarchived'
|
||||
// CHECK: @objc(_TtC{{.+[0-9]+}}ArchivedThenUnarchived)
|
||||
|
||||
// CHECK-LABEL: --ArchivedThenUnarchived2 start
|
||||
checkUnarchiving("ArchivedThenUnarchived2", ArchivedThenUnarchived.self)
|
||||
// CHECK-NEXT: --ArchivedThenUnarchived2 end
|
||||
|
||||
private class UnarchivedThenArchived {}
|
||||
|
||||
checkUnarchiving(UnarchivedThenArchived.self)
|
||||
// CHECK-NEXT: Attempting to unarchive Swift class '_Test.({{.+}}).UnarchivedThenArchived' 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]]--
|
||||
// CHECK-LABEL: --UnarchivedThenArchived1 start
|
||||
checkUnarchiving("UnarchivedThenArchived1", UnarchivedThenArchived.self)
|
||||
// CHECK: Attempting to unarchive Swift class '_Test.({{.+}}).UnarchivedThenArchived' with {{.+}} runtime name '_TtC{{.+[0-9]+}}UnarchivedThenArchived'
|
||||
// CHECK: @objc(_TtC{{.+[0-9]+}}UnarchivedThenArchived)
|
||||
|
||||
class Outer {
|
||||
// CHECK-LABEL: --UnarchivedThenArchived2 start
|
||||
checkArchiving("UnarchivedThenArchived2", UnarchivedThenArchived.self)
|
||||
// CHECK-NEXT: --UnarchivedThenArchived2 end
|
||||
|
||||
private 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]]--
|
||||
// CHECK-LABEL: --Outer.ArchivedTwice1 start
|
||||
checkArchiving("Outer.ArchivedTwice1", Outer.ArchivedTwice.self)
|
||||
// CHECK: Attempting to archive Swift class '_Test.({{.+}}).Outer.ArchivedTwice'
|
||||
// CHECK: @objc(_TtC{{.+[0-9]+}}ArchivedTwice)
|
||||
|
||||
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]]--
|
||||
// CHECK-LABEL: --Outer.ArchivedTwice2 start
|
||||
checkArchiving("Outer.ArchivedTwice2", Outer.ArchivedTwice.self)
|
||||
// CHECK-NEXT: --Outer.ArchivedTwice2 end
|
||||
|
||||
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]]--
|
||||
// CHECK-LABEL: --Outer.UnarchivedTwice1 start
|
||||
checkUnarchiving("Outer.UnarchivedTwice1", Outer.UnarchivedTwice.self)
|
||||
// CHECK: Attempting to unarchive Swift class '_Test.({{.+}}).Outer.UnarchivedTwice'
|
||||
// CHECK: @objc(_TtC{{.+[0-9]+}}UnarchivedTwice)
|
||||
|
||||
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]]--
|
||||
// CHECK-LABEL: --Outer.UnarchivedTwice2 start
|
||||
checkUnarchiving("Outer.UnarchivedTwice2", Outer.UnarchivedTwice.self)
|
||||
// CHECK-NEXT: --Outer.UnarchivedTwice2 end
|
||||
|
||||
// CHECK-LABEL: --Outer.ArchivedThenUnarchived1 start
|
||||
checkArchiving("Outer.ArchivedThenUnarchived1", Outer.ArchivedThenUnarchived.self)
|
||||
// CHECK: Attempting to archive Swift class '_Test.({{.+}}).Outer.ArchivedThenUnarchived'
|
||||
// CHECK: @objc(_TtC{{.+[0-9]+}}ArchivedThenUnarchived)
|
||||
|
||||
// CHECK-LABEL: --Outer.ArchivedThenUnarchived2 start
|
||||
checkUnarchiving("Outer.ArchivedThenUnarchived2", Outer.ArchivedThenUnarchived.self)
|
||||
// CHECK-NEXT: --Outer.ArchivedThenUnarchived2 end
|
||||
|
||||
// CHECK-LABEL: --Outer.UnarchivedThenArchived1 start
|
||||
checkUnarchiving("Outer.UnarchivedThenArchived1", Outer.UnarchivedThenArchived.self)
|
||||
// CHECK: Attempting to unarchive Swift class '_Test.({{.+}}).Outer.UnarchivedThenArchived'
|
||||
// CHECK: @objc(_TtC{{.+[0-9]+}}UnarchivedThenArchived)
|
||||
|
||||
// CHECK-LABEL: --Outer.UnarchivedThenArchived2 start
|
||||
checkArchiving("Outer.UnarchivedThenArchived2", Outer.UnarchivedThenArchived.self)
|
||||
// CHECK-NEXT: --Outer.UnarchivedThenArchived2 end
|
||||
|
||||
|
||||
private class 日本語 {}
|
||||
|
||||
checkArchiving(日本語.self)
|
||||
// CHECK-NEXT: Attempting to archive Swift class '_Test.({{.*}}).日本語'
|
||||
// CHECK-NEXT: @objc(_TtC{{.+[0-9]+}}9日本語)
|
||||
// CHECK-NEXT: @objc(ABCMyModel)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
checkArchiving(日本語.self)
|
||||
mark() // CHECK-NEXT: --[[@LINE]]--
|
||||
// CHECK-LABEL: --Japanese1 start
|
||||
checkArchiving("Japanese1", 日本語.self)
|
||||
// CHECK: Attempting to archive Swift class '_Test.({{.*}}).日本語'
|
||||
|
||||
// CHECK-LABEL: --Japanese2 start
|
||||
checkArchiving("Japanese2", 日本語.self)
|
||||
// CHECK-NEXT: --Japanese2 end
|
||||
|
||||
class ArchivedTwiceGeneric<T> {}
|
||||
func someFunction() {
|
||||
class LocalArchived: NSObject {}
|
||||
class LocalUnarchived: NSObject {}
|
||||
|
||||
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]]--
|
||||
// CHECK-LABEL: --LocalArchived start
|
||||
checkArchiving("LocalArchived", LocalArchived.self)
|
||||
// CHECK: Attempting to archive Swift class '_Test.({{.+}}).LocalArchived'
|
||||
|
||||
checkArchiving(ArchivedTwiceGeneric<NSObject>.self)
|
||||
// CHECK-NEXT: Attempting to archive generic Swift class '_Test.ArchivedTwiceGeneric<__C.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]]--
|
||||
// CHECK-LABEL: --LocalUnarchived start
|
||||
checkUnarchiving("LocalUnarchived", LocalUnarchived.self)
|
||||
// CHECK: Attempting to unarchive Swift class '_Test.({{.+}}).LocalUnarchived'
|
||||
}
|
||||
someFunction()
|
||||
|
||||
Reference in New Issue
Block a user