I a have a module, let’s call it MySDK that in a function returns an instance of Any. This SDK is consumed in a project of mine.
let theHatedAnyObject = MySDK().someAPI()
I know for sure that this instance is of a specific type of a dependency (Dependency) that it is also used in my project.
I'm trying to cast to that type, using an as! but it crashes, thus I'm trying to get a pointer from that struct and knowing the memory layout of that type trying to bind it from Any to that specific type.
I also tried to copy the memory but I get a crash.
Thread 1: EXC_BAD_ACCESS (code=2, address=0x1344e229
First attempt:
import Depencency
import MySDK
let theHatedAnyObject = MySDK().someAPI() // It's Any but the real type used in SDK is DependencyStruct exposed my Dependency
let stride = MemoryLayout<DependencyStruct>.stride
let alignment = MemoryLayout<DependencyStruct>.alignment
let byteCount = MemoryLayout<DependencyStruct>.size
let _ = withUnsafeBytes(of: &theHatedAnyObject) { bytes in
bytes.baseAddress!.withMemoryRebound(to: DependencyStruct.self, capacity: byteCount) { pointer in
let value = pointer.pointee // Crash here
}
}
Second attempt using memcpy:
import Dependency
import MySDK
let theHatedAnyObject = MySDK().someAPI() // It's Any but the real type used in SDK is DependencyStruct exposed my Dependency
let rawPointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)
let pointer = rawPointer.bindMemory(to: DependencyStruct.self, capacity: 1)
let _ = withUnsafeBytes(of: &theHatedAnyObject) { bytes in
memcpy(pointer, bytes.baseAddress, byteCount)
let value = pointer.pointee // Crash here
}
For the moment let's leave the deallocation aside.
The answer is. Is there any way to cast an object coming from another module from Any to a specific type knowing for sure the type and the memory layout of that specific object?
Related
This question already has answers here:
UnsafeMutablePointer Warning with Swift 5
(3 answers)
Warning: Initialization of 'UnsafeBufferPointer<T>' results in a dangling buffer pointer
(6 answers)
Initialization of 'UnsafeMutableRawPointer' results in a dangling pointer
(1 answer)
Closed 2 years ago.
So I have some code to create H264ParameterSets like:
var formatDesc: CMVideoFormatDescription?
func createH264FormatDescription(SPS: Array<UInt8>, PPS: Array<UInt8>) -> OSStatus {
if formatDesc != nil { formatDesc = nil }
let paramSet = [UnsafePointer<UInt8>(SPS), UnsafePointer<UInt8>(PPS)]
let paramPointers = UnsafePointer<UnsafePointer<UInt8>>(paramSet)
let paramSizes = UnsafePointer<Int>([SPS.count, PPS.count])
let status = CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramPointers, parameterSetSizes: paramSizes, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)
return status
}
Starting on Xcode 11.4 I got warnings for those UnsafePointer(), which seems not happen before:
Initialization of UnsafePointer<UInt8> results in a dangling pointer
Initialization of UnsafePointer<UnsafePointer<UInt8>> results in a dangling pointer
Initialization of UnsafePointer<Int> results in a dangling pointer
I'm not sure why we see this? and how can I remove the warning? Thank in advance.
The easiest way to explain this warning is to look at one of the cases causing it. So lets start with your use of SPS.
It is an Array<UInt8> so it is backed by a buffer of UInt8 just like in C. When you pass SPS with UnsafePointer<UInt8>(SPS) it creates a valid pointer to the buffer for that moment. The issue is that you could then mutate SPS say by appending another value to it. This would mean that the buffer backing the Array is potentially moved to another place in memory. This would mean that your pointer that is now part of paramSet is invalid.
The other issue is that if you pass this pointer to something, like you do in this case, the other function could try to hold onto it and then it has an invalid pointer. So if you expect the other function to hold onto the pointer you need to manually manage memory with UnsafePointers and Unmanaged yourself. If CMVideoFormatDescriptionCreateFromH264ParameterSets() doesn't hold onto the pointers then the code I'll share is correct, if it does you will need to adjust it to create/destory the memory as needed.
Also it is worth noting that in this case, you can't mutate any of the Arrays you have because they are constants but in general the principle is still the same. This means that in theory it could never be mutated but the Swift compiler prefers to help us write code that is always safe and correct whenever possible, even with UnsafePointer types.
So how can you fix this? You will need to be able to call withUnsafeBufferPointer and then access the pointer through the UnsafeBufferPointer like this:
var formatDesc: CMVideoFormatDescription?
func createH264FormatDescription(SPS: Array<UInt8>, PPS: Array<UInt8>) -> OSStatus {
if formatDesc != nil { formatDesc = nil }
let status = SPS.withUnsafeBufferPointer { SPS in
PPS.withUnsafeBufferPointer { PPS in
let paramSet = [SPS.baseAddress!, PPS.baseAddress!]
let paramSizes = [SPS.count, PPS.count]
return paramSet.withUnsafeBufferPointer { paramSet in
paramSizes.withUnsafeBufferPointer { paramSizes in
CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramSet.baseAddress!, parameterSetSizes: paramSizes.baseAddress!, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)
}
}
}
}
return status
}
The reason this approach works is that for the scope of withUnsafeBufferPointer the Law of Exclusivity is protecting the arrays so they can't be mutated.
If you are worried about the baseAddress! usage you can check that it isn't nil but it is guaranteed to not be nil when count > 0 according the the compiler engineers (they have stated this on either Twitter or the Swift forums I forget...).
I'm debugging my program to check if the value of a property is correctly set. I put a breakpoint in this function:
func showContent(data: Any) -> UIView {
// breakpoint here
var contentView = UIView()
if let image = data as? UIImage {
let imageView = UIImageView()
imageView.image = image
contentView = imageView
}
if let text = data as? String {
let label = UILabel()
label.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
label.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
label.text = text
contentView = label
}
return contentView
}
The value passed to this function is from a view controller:
override func viewDidLoad() {
calcGroupFamiliarity()
flashCardView.linkedMemory = Memory(masteryLevel: 1, algorithm: Algorithm.algorithm1.chooseAlgorithm(), forgetRatio: 0, lastStudyTime: Date(), front: #imageLiteral(resourceName: "Ideas-Blue"), back: #imageLiteral(resourceName: "Ideas-Yellow"))
}
As you can see, both the front and the back are images, however, in the debugger, they both appeared as some payload_data, while the data type of other values such as masteryLevel and algorithm are correct:
Can somebody explain what that means? And what should I do to pass the normal image data instead?
Update:
This is Memory class:
class Memory: NSObject, NSCoding {
var masteryLevel: Int
var algorithm: [Int: Double]
var forgetRatio: Int
var lastStudyTime: Date
var strength: Double = 0
var front: Any
var back: Any
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask)[0]
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("Memory")
init(masteryLevel: Int, algorithm: [Int: Double], forgetRatio: Int, lastStudyTime: Date, front: Any, back: Any){
self.masteryLevel = masteryLevel
self.algorithm = algorithm
self.forgetRatio = forgetRatio
self.lastStudyTime = lastStudyTime
self.front = front
self.back = back
}
...
}
This is a detail of how Swift implements the Any type. Given that Swift can't know beforehand what you are putting into e.g. front, it needs to store a pointer to that object (in payload_data_0) as well as a pointer to metadata about the stored object's type (in instance_type). As far as I know, payload_data_1 and payload_data_2 are there as an optimization so that Swift can store up to 24-byte large structs in place rather than having to put them in a wrapper object that needs to be stored in a separate heap location.
So, to answer your question: This is neither a bug in Swift, nor an error on your side. Your data gets stored and accessed in the right way. If you would prefer to inspect front more easily while debugging, try
(lldb) po front
in the debugger. Alternatively, change front's type to e.g. UIImage?. If that is not possible, you could declare a protocol
protocol MemoryStoreable: class { }
and extend every type that needs to be stored in those fields like so:
extension UIImage: MemoryStoreable { }
and declare the variable as
var front: MemoryStoreable?
Note that MemoryStoreable is restricted to classes, as otherwise a protocol witness would need to be stored (which is again implemented similarly to the Any field you observed).
Another alternative would be, if you know you'll store e.g. only images and strings in front, to use an enum:
enum MemoryStoreable {
case `string`(String)
case image(UIImage)
}
var front: MemoryStoreable?
That will still require at least 25 bytes of storage in your object (possibly 32 due to memory alignment constraints), though, as that's the size of the String struct.
For more details, see e.g. https://mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html.
Base on #MrMage's awesome answer.
#MrMage gave 2 suggestions on approaching this problem, it took some
effort for me to grasp them, for what I wanted to achieve in the
program, it's the perfect scenario for me to go
protocol-oriented-programming.
Here's a really easy to understand tutorial that help me a lot:
Introduction to Protocol Oriented Programming in Swift
Hopefully this can be helpful to you :)
After I convert from swift 2 to swift 3, there is an error pop up for the below metioned line
let value = UnsafePointer<UInt32>(array1).pointee
'init' is unavailable: use 'withMemoryRebound(to:capacity:_)' to temporarily view memory as another layout-compatible type.
in swift2 it is like
let value = UnsafePointer<UInt32>(array1).memory
Can someone explain please?
Sorry I'm quite new to swift3
After i have make the changes to
let abc = UnsafePointer<UInt32>(array1).withMemoryRebound(to: <#T##T.Type#>, capacity: <#T##Int#>, <#T##body: (UnsafeMutablePointer<T>) throws -> Result##(UnsafeMutablePointer<T>) throws -> Result#>)
but still what value should go in to the variable? Sorry, i have search around but too bad i can't find a solution
You can try this:
let rawPointer = UnsafeRawPointer(array1)
let pointer = rawPointer.assumingMemoryBound(to: UInt32.self)
let value = pointer.pointee
Raw pointer is a pointer for accessing untype data.
assumingMemoryBound(to:) can convert from an UnsafeRawPointer to UnsafePointer<T>.
Reference :Swift 3.0 Unsafe World
If array is an Array, your best bet is to use withUnsafeBufferPointer:
array.withUnsafeBufferPointer { buffer in
// do something with 'buffer'
// (if you need an UnsafePointer rather than an UnsafeBufferPointer,
// you can access that via the buffer's .baseAddress property)
}
Make sure you don't let the buffer pointer escape from the closure, because it will not be valid outside it.
I'm using the Swift compiler's Bridging Header feature to call a C function that allocates memory using malloc(). It then returns a pointer to that memory. The function prototype is something like:
char *the_function(const char *);
In Swift, I use it like this:
var ret = the_function(("something" as NSString).UTF8String)
let val = String.fromCString(ret)!
Forgive my ignorance concerning Swift but normally in C, if the_function() is malloc'ing memory and returning it, somebody else needs to free() it at some point.
Is this being handled by Swift somehow or am I leaking memory in this example?
Thanks in advance.
Swift does not manage memory that is allocated with malloc(), you have to free the memory eventually:
let ret = the_function("something") // returns pointer to malloc'ed memory
let str = String.fromCString(ret)! // creates Swift String by *copying* the data
free(ret) // releases the memory
println(str) // `str` is still valid (managed by Swift)
Note that a Swift String is automatically converted to a UTF-8
string when passed to a C function taking a const char * parameter
as described in String value to UnsafePointer<UInt8> function parameter behavior.
That's why
let ret = the_function(("something" as NSString).UTF8String)
can be simplified to
let ret = the_function("something")
I have the following class:
class RawDataArray {
var data: NSData!
init(filePath: String) {
data = NSData(contentsOfFile: filePath)
}
func read<T>(offset: Int) -> T {
return UnsafePointer<T>(data.bytes + offset).memory
}
}
which I use in my iOS app to read from a binary file with a custom format. For example, to read an Int at offset 5, I use:
let foo = rawData.read(5) as Int
This works in the simulator, on my iPad Air, and has passed the review for beta testing. However, my external testers have iPad 2s and 4s, and they are getting EXC_ARM_DA_ALIGN errors.
I cannot change the structure of the input file. Is there any way to fix the read function to make sure the objects are built from properly aligned memory locations?
Yes, reading unaligned data can fail on some architectures.
A safe method is to copy the bytes into a (properly aligned) variable.
So the first idea would be to write a generic function:
func read<T>(offset: Int) -> T {
var value : T
memcpy(&value, data.bytes + offset, UInt(sizeof(T)))
return value
}
However, this does not compile, because the variable value
must be initialized before being used, and there is no default
constructor that can be used to initialize a complete generic
variable.
If we restrict the method to types which can be initialized with 0 then we get this:
func read<T : IntegerLiteralConvertible>(offset: Int) -> T {
var value : T = 0
memcpy(&value, data.bytes + offset, UInt(sizeof(T)))
return value
}
This works for all integer and floating point types.
For the general case, one could use the idea from https://stackoverflow.com/a/24335355/1187415:
func read<T>(offset: Int) -> T {
// Allocate space:
let ptr = UnsafeMutablePointer<T>.alloc(1)
// Copy data:
memcpy(ptr, data.bytes + offset, UInt(sizeof(T)))
// Retrieve the value as a new variable:
var item = ptr.move()
// Free the allocated space
ptr.dealloc(1)
return item
}
Swift allows to overload functions with different return types,
therefore you can actually define both methods, and the compiler will
automatically choose the more restrictive one.
FOR ALL SWIFT DEVELOPERS THAT READ THIS
I had a different case with the same crash type!
If your project is defined for (old?) swift version, but you link against a cocoa pod (in my case) that has a different (newer?) swift version in build settings - you’re going to get that crash even when you just trying to set a value of a public field in a regular swift struct.
The stack trace will tell you nothing and may show you the wrong place (CAAnimation free memory for example)
In my case my project was in Swift 4, and my pod (SwiftMessages) was Swift 4.2
AND BOOM!!!
Happy coding!