Accessing CFArray causes crash in Swift - ios

My following code crashes with EXC_BAD_ACCESS, and I do not understand why. My initial understanding is that the memory retention in this case should be automatic, but it seems I am wrong... Maybe someone can help. Thank you! The code is written in Swift 5 and runs on iOS 15.2 in XCode 13.2.1.
Casting to NSArray causes trouble...
let someFont = CGFont("Symbol" as CFString)!
if let cfTags: CFArray = someFont.tableTags {
let nsTags = cfTags as NSArray
print(nsTags.count) // Prints: 16
let tag0 = nsTags[0] // CRASH: Thread 1: EXC_BAD_ACCESS (code=257, ...)
}
Alternatively, using CFArray-API causes also trouble (The crash message is about a misaligned pointer but the root cause seems also the bad access, which occurs e.g. if I replace UInt32.self by UInt8.self, and hence eliminate the alignment problem).
let someFont = CGFont("Symbol" as CFString)!
if let cfTags: CFArray = someFont.tableTags {
print(CFArrayGetCount(cfTags)) // Prints: 16
let tag0Ptr: UnsafeRawPointer = CFArrayGetValueAtIndex(cfTags, 0)!
tag0Ptr.load(as: UInt32.self)// CRASH :Thread 1: Fatal error: load from misaligned raw pointer
}

The issue here is that the CGFont API uses some advanced C-isms in their storage of table tags, which really doesn't translate to Swift: CGFontCopyTableTags() returns a CFArrayRef which doesn't actually contain objects, but integers. (This is technically allowed through CFArray's interface: it accepts void *s in C, into which you can technically stuff any integer which fits in a pointer, even if the pointer value is nonsense...) Swift expects CFArrays and NSArrays to only ever contain valid objects and pointers, and it treats the return values as such — this is why accessing via NSArray also fails (Swift expects an object but the value isn't an object, so it can't be accessed like a pointer, or retained, or any of the things that the runtime might expect to do).
Your second code snippet is closer to how you'll need to access the value: CFArrayGetValueAtIndex appears to return a pointer, but the value you're getting back isn't a real pointer — it's actually an integer stored in the array directly, masquerading as a pointer.
The equivalent to the Obj-C example from the CGFontCopyTableTags docs of
tag = (uint32_t)(uintptr_t)CFArrayGetValue(table, k);
would be
let tag = unsafeBitCast(CFArrayGetValueAtIndex(cfTags, 0), to: UInt.self)
(Note that you need to cast to UInt and not UInt32 because unsafeBitCast requires that the input value and the output type have the same alignment.)
In my simulator, I'm seeing a tag with a value of 1196643650 (0x47535542), which definitely isn't a valid pointer (but I don't otherwise have domain knowledge to validate whether this is the tag you're expecting).

Related

How to avoid crashes in tableView(_:cellForRowAt)?

In the Fabric, I see a lot of crashes in the "tableView(_:cellForRowAt)" section. There is not a certain scenario for this exceptions. Anytime and in the any screen it can occur.There is no data for analysing the crashes. Only I know there are crashes in "tableView(_:cellForRowAt)".
I want to prevent this kind of exceptions although I do not know the root cause. Can I use a method like preventing NullPointer Exception (if (!null)) ?
Below two crashes in the different code sections ;
let XXX = Constants.sharedInstance.url+"/service/photo/"+userdas[(indexPath as NSIndexPath).row].id!+"/"+userdas[(indexPath as NSIndexPath).row].photo!+"/2"
and
self.notificationModel[indexPath.row].userNot.XXX?.XXXImageView = image
From your code, it's clear that you're making a couple of explicit force unwraps that could lead you to crash.
userdas[(indexPath as NSIndexPath).row].id!
userdas[(indexPath as NSIndexPath).row].photo!
self.notificationModel[indexPath.row].userNot.XXX?.XXXImageView
I guess that in the third case XXXImageView is implicitly unwrapped UIImageView that also might be nil.
To avoid the crash in your first section you can use a guard
guard let id = userdas[indexPath.row].id,
let photo = userdas[indexPath.row].photo else {
return
}
let XXX = Constants.sharedInstance.url+"/service/photo/"+id+"/"+photo+"/2"
I'm not sure what you're doing in the second section, but you just need to check that you unwrapped parameters aren't nil as well

OBJ-C wipe NSData content before nullifying it

For security reasons we need Always to wipe sensitive data from memory.
Usually it is not something that i see done in IOS but for apps and need extended security it is very important.
The Data that Usually needs to be wiped if NSData and NSString objects (pointing to nil does not wipe the data and it is a security breach)
I've managed to wipe my NSStrings with the code below (When password is NSString):
unsigned char *charPass;
if (password != nil) {
charPass = (unsigned char*) CFStringGetCStringPtr((CFStringRef) password, CFStringGetSystemEncoding());
memset(charPass, 0, [password length]);
password = nil;
}
Big remark on this implementation: You HAVE to check for NULL before calling the charPass or it might crash. There is NO guarantee that CFStringGetCStringPtr will return a value!
When password is NSData It suppose to be even more strait forward and the code bellow suppose to work:
memset([password bytes], 0, [password length]);
But this gives me a compilation error:
No matching function for call to 'memset'
I can't find a workaround to point to the password address and wipe the bytes over there like I did with the string (bytes method should let me do just that from what I understand but it doesn't compile for some reason that I cant figure out)
Any one has an idea for this?
10x
Your string deallocator is fragile. You write:
Big remark on this implementation: You HAVE to check for NULL before calling the charPass or it might crash. There is NO guarantee that CFStringGetCStringPtr will return a value!
This is documented behaviour as CFString (and hence NSString) does not guarantee you direct access to its internal buffer. You don't say what how you handle this situation, but if you don't erase the memory you presumably have a security problem.
In the case you do get a valid pointer back you are using the wrong byte count. The call [password length] returns:
The number of UTF-16 code units in the receiver.
which is not the same as the number of bytes. However CFStringGetCStringPtr returns:
A pointer to a C string or NULL if the internal storage of theString does not allow this to be returned efficiently.
If you have a C string you can use C library function strlen() to find its length.
To address the case when CFStringGetCStringPtr returns NULL you could create the string yourself as a CFString and supply a custom CFAllocater. You shouldn't need to write a complete allocator yourself, instead you could build one based on the system one. You can get the default allocators CFAllocatorContext which will return you the function pointers the system uses. You can then create a new CFAllocator based of a CFAllocatorContext which is a copy of the default one except you've changed the deallocate and reallocate pointers to functions which you have implemented in terms of the default allocate, reallocate and deallocate but also call memset appropriately to clear out memory.
Once you've done that doing your security wipe comes down to making sure these custom created CFString objects, aka NSString objects, are deallocated before your app quits.
You can find out about CFAllocator, CFAllocatorContext etc. in Memory Management Programming Guide for Core Foundation.
Which brings us to your actual question, how to zero an NSData. Here you are in luck an NSData object is a CFData object, and CFData's CFDataGetBytePtr, unlike CFStringGetCStringPtr, is guaranteed to return a pointer to the actual bytes, straight from the documentation:
This function is guaranteed to return a pointer to a CFData object's internal bytes. CFData, unlike CFString, does not hide its internal storage.
So code following your pattern for CFString will work here. Note that using NSData's bytes is not guaranteed in the documentation to call CFDataGetBytePtr, it could for example call CFDataGetBytes and return a copy of the bytes, use the CFData functions.
HTH
While I cannot speak for the actual safety of doing this, your problem is that NSData's bytes method returns a const void *
https://developer.apple.com/documentation/foundation/nsdata/1410616-bytes?language=objc
You can cast it to a void * if you want by
memset((void *)[password bytes], 0, [password length]);
If you use a NSMutableData, you won't have to do this.

Swift Dictionary Absurd Memory Usage

I ran into an interesting problem in one of my applications. When accessing a Dictionary many times, the memory usage of my application skyrockets to over a gigabyte in seconds. Here is some sample code to show the problem.
override func viewDidLoad() {
let dictionary = ["key1":"value1"]
let nsKey: NSString = "key1"
let swiftKey = nsKey as String
for _ in 0 ... 10000000 {
dictionary[swiftKey]
}
}
Repeatedly accessing the dictionary causes memory to climb until the loop finishes. I looked at instruments and saw tons of string allocations. Turns out using an NSString is the issue.
Changing the nsKey to a swift String like so fixes the issue:
let nsKey = "key1"
Also changing the dictionary to an NSDictionary fixes the issue:
let dictionary: NSDictionary = ["key1":"value1"]
Does anyone know why accessing the dictionary using a casted NSString causes so much heap allocation, and are there any other fixes besides the ones described above?
Here are some pictures. It looks like behind-the-scenes strings are being allocated and set to autorelease (or am I reading the data below wrong?) Could this be why memory usage continuously allocates and then drains at a later point? If this is true, should this be considered a "bug"? This issue occurs on OS X as well as iOS.
The best solution is to not bridge to NSString here. Just use Swift types. Or, as you discovered, you can just use Foundation types (NSString and NSDictionary). Bridging can require making temporary copies.
In any case, though, in loops like this it's very common to create temporary copies for one reason or another (even if you avoided this particular problem). To address that, you need to drain your autorelease pool in the loop. For instance:
let dictionary = ["key1":"value1"]
let nsKey: NSString = "key1"
let swiftKey = nsKey as String
for _ in 0 ... 10000000 {
autoreleasepool { // <=== the scope of the current pool
dictionary[swiftKey]
}
}
Adding that will keep your memory steady. This is a very common thing to do in large loops in Cocoa. Otherwise the pool won't be drained until you return from your top-level method.

Casting generic array in swift cause fatal error

I have a class which acts as a BLL, wrapping a service protocol. The service protocol provides a list of SerializableObjectProtocol objects. For instance, I have User, which implements SerializedObjectProtocol.
The following function casts a SerializedObjectProtol array into a User
public func Get() -> [T]
{
let result = self._service.Get()
return result as! [T]
}
As a result, I am getting the following error:
array element cannot be bridged to Objective-C
I am aware that the code is error prone, because if the object is not T, down casting cannot happen. As a result, here is what I can verify:
T in constrained to implement SerializedObjectProtol i.e.
class DataLayer<T:SerializableObjectProtocol>
T is type User. The result is an array of user. i.e. [User]
I can get around this issue, but I have to manually cast each item. As a result, this works perfectly fine:
var returnArray = [T]()
for item in result
{
returnArray.append(item as! T)
}
return returnArray;
I have just picked up Swift for a project so I have limited experience with it. As a result, I have gone out to see if what I am trying is possible (casting array [S] to [T]). It seems that it is possible if the array is [Any].
Is this a valid operation in Swift? Or is casting this way not possible.
Generally it is not possible to cast directly between an array of Any to the type it contains, because Any has a completely different representation in memory: sizeof(Any) isn't equal to sizeof(User)! An array of 10 Anys might have a length of 320 bytes, but 10 Users only need 80 bytes, the same applies to any protocol. Conclusion: You need to cast every single item.
Maybe do it like this:
return results.map{ $0 as! User }
or if you're not sure whether every item is a User, you can only return the Users like this:
return results.flatMap{ $0 as? User }
If you're still having problems, please post some minimal code that still produces the error, it's really hard to understand what your code looks like without the actual code

NSError object already populated on first method call

I am following the book Test-Driven iOS development by G. Lee and came across this unit test, which I don't understand. First of all, if you need more code, please let me know right away.
-(void)testDelegateNotifiedOfErrorWhenNewsBuilderFails
{
MockNewsBuilder *builder = [MockNewsBuilder new];
builder.arrayToReturn = nil;
builder.errorToSet = underlyingError;
newsManager.newsBuilder = builder;
[newsManager receivedNewsJSON:#"Fake Json"];
...
}
-(void)receivedNewsJSON:(NSString *)objectNotation
{
NSError *error = nil;
// As you see error is nil and I am passing in a nil error.
NSArray *news = [_newsBuilder newsFromJSON:objectNotation error:&error];
...
}
#implementation MockNewsBuilder
-(NSArray *)newsFromJSON:(NSString *)objectNotation error:(NSError **)error
{
// But once I arrive here, error is no longer nil.
// (NSError **) error = 0x00007fff5cb887f0 domain: #"Fake Json" - code: 0
...
}
How is error auto-magically set?
UPDATE:
Thanks everyone for active discussion and advice. The answers explain how the caller side gets the error instance because of &, I understand that clearly. My question remains though why the callee side is pointing to a populated NSError instance, even though it had to be nil. I didn't set the error instance within newsFromJSON:error: so how is it already populated there?
I just changed [newsManager receivedNewsJSON:#"Fake Json1"]; and the error instance within newsFromJSON:error: reflects right away
(NSError **) error = 0x00007fff5b9b27f0 domain: #"Fake Json1" - code: 0. Its very confusing...
This is just pointer to pointer concept. You are passing the reference to the reference error object &error to the method -(NSArray *)newsFromJSON:(NSString *)objectNotation error:(NSError **)error;
And this will update the error object at the memory pointer you have passed.
See this is the concept of pointer to pointer.
Update:
Your error object is nil, yes its right. But you are not passing that error object to the newsFromJSON method, but the memory address of the error object( &error). That is the memory address of the error object.
This why you are getting non null value there inside your newsFromJSON method.
And one more thing, you can access the original object in your newsFromJSON method using the content of operator(* operator)
like **error = something;
This will update your original object ( NSError *error ) you declared in your caller method.
In C or CPP or Objective-C, & is the Address of operator and * is the content of operator.
&obj -> give the memory address of the obj
*obj -> give the content of the memory address in the obj.
** is a pointer to a pointer.
It means you need to pass a pointer address to a function or method.
Objective-C is a strict superset of C.
That means as in C functions and methods can only return one value.
There are two ways about it.
One is to wrap all your returns in structs or NSDictionaries or other collections.
This way is called an outParameter
It's passing a pointer address in.
C is a by copy language. But pointers are portable black holes that allow you to do wild things in C.
Objective-C and C++ give you the same wildness.
The error is set by Apple's framework code.
The Cocoa pattern is usually to return a BOOL and pass in an NSError pointer address.
If BOOL is NO check the NSError.
Apple framework will have put some presents in your NSError pointer address box.
Sometimes they don't use BOOL and instead return an object or nil.
Core Foundation C frameworks work very similarly and use in and out parameters a lot.
error is a variable of type NSError*, that is "pointer to NSError" (in objective-C, all objects are handled as references, as opposed to e.g. C++).
What this means is that error is a (local) variable that stores the address of the actual NSError object, initially nil.
The method you call creates an (autoreleased) NSError instance. In order to get a reference to that instance back, you need to pass the method the address of the pointer, or &error, which is, in turn, of type "pointer to pointer to NSError" (note the two-level indirection).
You do this because arguments to functions in C and methods in Objective-C are passed by value: if you just passed error, the value stored there (nil) alone is copied, and no matter what the called method does, the contents of the variable error on your side (the caller) can't be modified. To achieve this, you need to pass the address of error, or &error.
This way, the called method can "change" the contents of error (the address held there) so that it points to the newly created, NSError instance.
Does it make sense?
ADDENDUM: This is a very common pattern very often seen in Cocoa: The method being called could potentially fail, and instead of just using the return value to signal success/failure, and additional 'in/out' parameter is passed to retrieve detailed error information in case of failure. On failure, the method can return false (NO, 0, etc.), but in addition in can provide a more detailed error report (e.g. the reason for failure) inside the NSError instance.
EDITED: As #Droppy said, and seeing that all code involved is your own (i.e., not some first or third party framework), it is impossible that error is set to anything other than nil unless you explicitly allocate it somewhere. Perhaps you should "watch" it in the debugger to see when/where it is set. since the message seems to be set to #"Fake JSON", the first thing you could do is search that string in your project (all files).

Resources