New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SR-12344] Xcode 11.4.beta3 - objc_copyClassList Returns Invalid Data #54778
Comments
Comment by Mikhail Lobanov (JIRA) I've got the same issue. |
Comment by Sergei Shirokov (JIRA) Me too. No idea how to fix this. |
Comment by Kris Kline (JIRA) Unfortunately it looks like it would have to be fixed by Apple...don't see anything we can do about it. 🙁 |
Comment by Kris Kline (JIRA) Attached a sample project showing the problem (also attached the project to the ticket logged with Apple) |
Comment by Kris Kline (JIRA) To get around it for now, we re-architected a bit and now use the class name and pass it to NSStringFromClass. It is not near as elegant 🙁 |
Comment by Kris Kline (JIRA) Haven't heard anything since the initial response that requested the sample project... |
Comment by Kris Kline (JIRA) Xcode 11.4.1 was released today (or yesterday...not sure), looks like this might be fixed...
Still working on installing and then will try it out... |
Comment by Kris Kline (JIRA) False hope...not fixed. 🙁 |
What y'all are noticing is that loads involving AutoreleasingUnsafeMutablePointer still involve ARC traffic against unknown objects. Unfortunately, that means that this access `allClasses?[i]` was always doomed to fail. Let's take a step back // SomeObjCFramework.h
OBJC_ROOT_CLASS @interface MyRootClass
@end
// SomeObjCFramework.m
@implementation MyRootClass
@end
// SomeSwiftCode.swift
import SomeObjCFramework
print(MyRootClass.self) // Explodes! This code explodes for the same reason, but demonstrates the issue with a simpler example. Swift attempts to manipulate the metaclass MyRootClass.self as though it were any other "unknown object" reference by sending them `swift_unknownObjectRetain` and `swift_unknownObjectRelease`. Unfortunately, though `MyRootClass` implements enough to convince the Objective-C and Swift runtimes that it is a class, it is really a ticking time bomb. The first message we send to this object is going to blow up because it neither implements nor forwards the message. Returning to your code, there is a hidden `swift_unknownObjectRetain` to try to balance out the +0 load from the pointer, and that retain occurs on the line you called out. Since the object doesn't respond to or forward `retain`, the default forwarding handler installed by CF has no choice but to abort. So, what can you do about it? Well, if ARC is the problem, then skirting ARC is the solution. You'll need to drop down to raw pointer accesses var nc: UInt32 = 0
if let classes = objc_copyClassList(&nc) {
let ptr = UnsafePointer<AnyClass>(classes)
for i in (0 ..< Int(nc)) {
print(ptr[i])
}
free(UnsafeMutableRawPointer(classes))
} |
Note that the above "works" by convincing the compiler we have the appropriate __strong references to the metaclasses in this buffer, so it doesn't need to defensively retain on the access. This is a lie that has very bad consequences in optimized code. The most correct answer would be to reinterpret the class list as `UnsafePointer<Unmanaged<AnyClass>>`, but this type is impossible to form as `AnyObject.Type` does not satisfy class bounds. |
Comment by Kris Kline (JIRA) What @CodaFi commented does seem to work just fine. Changing the return type to an UnsafePointer<AnyClass>, and then freeing as an 'UnsafeMutableRawPointer'. |
Comment by Leslie Godwin (JIRA) @CodaFi 's example does not work for me when I try add the type to a list (I guess retaining it). So this works: for i in (0 ..< Int(nc)) {
print(ptr[I]) // OK
} But if you try do something else with the type (not just a print()), maybe add to an array... for i in (0 ..< Int(nc)) {
results.append(ptr[I]) // CRASH: Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
} |
Comment by Leslie Godwin (JIRA) I've just created a pure Objective-C implementation and it has the same issue: + (NSArray<Class> *) allTypes
{
NSMutableArray *results = [NSMutableArray array];
int numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0 )
{
Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++)
{
[results addObject: classes[i]];
// CRASH: Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
}
free(classes);
}
return [results copy];
} |
Comment by Leslie Godwin (JIRA) The class that causes the crash is `PFEmbeddedMulticasterImplementation` |
Comment by Kris Kline (JIRA) raygun (JIRA User) - You might want to try calling objc_copyClassList(), instead of objc_getClassList(). It's been working fine for us since changing to adapt to @CodaFi's suggestion. |
Resolving this issue since it is not specific to Swift, it is specific to the set of frameworks you have loaded into your process at runtime. `PF`-prefixed classes, for example, come from CoreData. |
Comment by Leslie Godwin (JIRA) kkline (JIRA User) , in a playground this still crashes, using `objc_copyClassList`:
It prints:
and then crashes.
import Foundation
var typeCount: UInt32 = 0
if let classes = objc_copyClassList(&typeCount)
{
let types = UnsafePointer<AnyClass>(classes)
var result: [AnyClass] = []
for index in 0 ..< Int(typeCount)
{
print(types[index])
result.append(types[index]) // error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
}
print(result)
free(UnsafeMutableRawPointer(classes))
} (The first attachment is incorrect, don't know how to delete it) |
Comment by Kris Kline (JIRA) raygun (JIRA User) - Actually...I think you're issue may be that you aren't appending to the array, you are setting at an unallocated index.. Maybe try changing that line to - result.append(types[index]) Just guessing that might fix the problem... |
Comment by Leslie Godwin (JIRA) Sorry my bad, I just deleted the incorrect attachment now. I couldn't find how to when I logged the comment. This is the dilemma: print(types[index])
result.append(types[index]) // error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). If you and a look at the attachment again, (sorry) you'll see that `print()` does print the name. ("JSExport" is printed in the playground console) So print works....?, it finds a class and printed its name> Actually the first class found. SO, `print` works, but `append` crashes. |
Comment by Kris Kline (JIRA) raygun (JIRA User) - I'm not using a playground, but in a library, this still works just fine for me- (called from a consuming app, or from unit tests) static func objc_getAllClasses() -> [AnyClass] {
var actualClassCount = UInt32(0)
// Following the suggestions/details from the SWIFT issue: https://bugs.swift.org/browse/SR-12344
let allClasses = UnsafePointer<AnyClass>(objc_copyClassList(&actualClassCount))
var classes: [AnyClass] = []
for i in 0 ..< Int(actualClassCount) {
if let currentClass: AnyClass = allClasses?[i] {
print(currentClass)
classes.append(currentClass)
}
}
free(UnsafeMutableRawPointer(mutating: allClasses))
return classes
} There is a chance that if you're just doing this in a 'playground', it may not like the objc class inspection logic... |
Comment by Leslie Godwin (JIRA) kkline (JIRA User), Thank you, 😃 only saw your reply now. Your solution works. 😉 It took me a while to spot the difference: `objc_copyClassList`.... |
Comment by Leslie Godwin (JIRA) kkline (JIRA User) public static func allTypes() -> [AnyObject.Type]
{
let typeCount = Int(objc_getClassList(nil, 0))
let types = UnsafeMutablePointer<AnyObject.Type>.allocate(capacity: typeCount)
defer { types.deallocate() }
let safeTypes = AutoreleasingUnsafeMutablePointer<AnyObject.Type>(types)
objc_getClassList(safeTypes, Int32(typeCount))
return [AnyObject.Type](unsafeUninitializedCapacity: typeCount)
{
buffer, initializedCapacity in
for index in 0 ..< typeCount
{
buffer[index] = types[index] // <---- if you use `safeTypes` it crashes...
}
initializedCapacity = typeCount
}
} Does this sound correct? Why when accessing from `safeTypes` is it breaking? Should it still not have worked? You'll notice in your example by remove the UnsafePointer() wrapping, that it will also crash. |
Comment by Kris Kline (JIRA) At least in the code you put...if you're intentionally creating 'safeTypes' from 'types' and then loading the class list into safeTypes...that could be doing some weird things... I would just suggest going from the code I posted, as that appears to work good for us (and work on Xcode 12.2). -Thanks |
Comment by Leslie Godwin (JIRA) Seems Xcode 13 Beta3 is now again returning class types that give the error: Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) when casting. This code is working in Xcode 12.5.1, (if I re-compile in Xcode 13 beta3 this crashes) protocol ITest
{
}
struct ObjC
{
public static func allTypes() -> [AnyObject.Type]
{
var _typesCount = UInt32(0)
let typesPointer = UnsafePointer(objc_copyClassList(&_typesCount))!
let typesCount = Int(_typesCount)
return [AnyObject.Type](unsafeUninitializedCapacity: typesCount)
{
buffer, initializedCapacity in
for index in 0 ..< typesCount
{
// this line was inserted to show the error
typesPointer[index] is ITest.Type //<----- CRASHES HERE !
buffer[index] = typesPointer[index]
}
initializedCapacity = typesCount
}
}
} |
raygun (JIRA User) I want to reiterate that this is not a compiler problem and your code was always incorrect - the OS version and installed frameworks your code is executing against is the variable here, not the compiler. You may not use Swift casting primitives to detect the dynamic type of these raw classes for the same reasons outlined above - an `is` check may be implemented as a conditional-cast-then-check-for-nil which is always going to occur at +1. You must manipulate these values without incurring ARC traffic of any kind or dispatching through runtime primitives that do not expect these values. |
Attachment: Download
Additional Detail from JIRA
md5: 4672a6d522080a19e260c94b01ceb33f
Issue Description:
We are getting a crash when running this code -
The Crash we see is on the line:
if let currentClass: AnyClass = allClasses?[i] {
We are getting these errors printed out, right before it crashes
It seems the problem is that the array returned by objc_copyClassList is either filled with invalid objects, or somehow getting cleaned up right after it gets created, because if I try to access the objects in
allClasses
asAny
, it crashes as soon as it tries to access.-Thanks
The text was updated successfully, but these errors were encountered: