[swift-users] Swift-C array interop with Swift 4/Xcode 9

Johannes Weiß johannesweiss at apple.com
Thu Sep 21 05:05:04 CDT 2017


Hi Rick,

> On 21 Sep 2017, at 1:03 am, Rick Mann via swift-users <swift-users at swift.org> wrote:
> 
> I've got Swift code wrapping a C API. One of the C structs the API uses looks like this:
> 
> typedef struct {
>    size_t size;
>    float transformation[16];
>    float projection[16];
>    uint32_t width;
>    uint32_t height;
> } image_info_t;
> 
> In Swift, the two array members are 16-tuples of floats. Eventually, the Swift code calls an Objective-C delegate, passing a tuple to an array:
> 
> self.delegate?
>    .imageReceived(self,
>                    index: inReq.imageIndex,
>                    data: data,
>                    width: width,
>                    height: height,
>                    transform: &inReq.imageInfo.transformation.0,
>                    projection: &inReq.imageInfo.projection.0)

these are illegal. You're getting a pointer to _only_ the first element (&inReq.imageInfo.transformation.0) but the library will then read 16 elements from there.

Something like this should work (and is legal):

withUnsafeMutablePointer(to: &inReq.imageInfo.transformation) { tupleOfFloatsPtr in
    tupleOfFloatsPtr.withMemoryRebound(to: Float.self, capacity: 16) { arrayOfFloatsPtr in
        ....imageReceived(..., transform: arrayOfFloatsPtr, ...)
    }
}

that's legal because now we're capturing a pointer to the whole tuple and therefore Swift guarantees the memory to be in C standard layout.



> 
> This seems to have been working fine for a long time, but we're running into a crash in some cases, and so I've been digging into it. I turned on the address sanitizer, and I consistently get it to trip in some C++ code that eventually gets called with a float* to that transform tuple.
> 
> Now, in Swift, the values of the tuple look great:
> 
> ▿ 16 elements
>  - .0 : 0.548282921
>  - .1 : 0.821425974
>  - .2 : -0.156990349
>  - .3 : 0.0
>  - .4 : -0.277842015
>  - .5 : 0.00185899995
>  - .6 : -0.960625231
>  - .7 : 0.0
>  - .8 : -0.788789928
>  - .9 : 0.570312977
>  - .10 : 0.229245573
>  - .11 : 0.0
>  - .12 : 0.0475889929
>  - .13 : -0.0323969983
>  - .14 : -0.0113521963
>  - .15 : 1.0
> 
> I can see this by clicking up the stack a couple frames to the Swift code. But the Objective-C(++) delegate code, that gets passed a float*, sees this (BTW, I can't figure out how to get the debugger, Xcode or command line, to display inTransform as an array of 16 floats). It's clearly not the same array.
> 
> 
> (lldb) po inTransform[0]
> 0.548282921
> 
> (lldb) po inTransform[1]
> 0.0343905278
> 
> (lldb) po inTransform[2]
> -0
> 
> (lldb) po inTransform[3]
> 0
> 
> (lldb) po inTransform[4]
> 0
> 
> (lldb) po inTransform[5]
> 0
> 
> (lldb) po inTransform[6]
> 95488384
> 
> (lldb) po inTransform[14]
> 0.00000000000000000000000000000000000000000000560519386
> 
> (lldb) po inTransform[15]
> 0
> 
> The address sanitizer spits out this:
> 
> =================================================================
> ==1358==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x00014d3ea224 at pc 0x000100eb9a84 bp 0x00016fd84bd0 sp 0x00016fd84bc8
> READ of size 4 at 0x00014d3ea224 thread T0
>    #0 0x100eb9a83 in <redacted>::addImage(void const*, int, int, int, float const*, float const*) (<redacted>:arm64+0x100e41a83)
>    #1 0x10040b72b in -[<redacted> imageReceived:index:data:width:height:transform:projection:] (<redacted>:arm64+0x10039372b)
>    #2 0x10045d037 in function signature specialization <Arg[0] = Owned To Guaranteed, Arg[1] = Owned To Guaranteed, Arg[2] = Owned To Guaranteed and Exploded> of closure #1 () -> () in <redacted>.imageDownloaded(<redacted>.DownloadImageRequest, index: Swift.Int) -> () (<redacted>:arm64+0x1003e5037)
>    #3 0x100442bbb in closure #1 () -> () in <redacted>.imageDownloaded(<redacted>.DownloadImageRequest, index: Swift.Int) -> () (<redacted>:arm64+0x1003cabbb)
>    #4 0x10043e7fb in reabstraction thunk helper from @callee_owned () -> () to @callee_unowned @convention(block) () -> () (<redacted>:arm64+0x1003c67fb)
>    #5 0x103ba9d5b in __wrap_dispatch_async_block_invoke (/var/containers/Bundle/Application/B3E557CA-5ED9-4673-8B07-37A0F2DA472B/<redacted>.app/Frameworks/libclang_rt.asan_ios_dynamic.dylib:arm64+0x4dd5b)
>    #6 0x105f1549b in _dispatch_call_block_and_release (/usr/lib/system/introspection/libdispatch.dylib:arm64+0x149b)
>    #7 0x105f1545b in _dispatch_client_callout (/usr/lib/system/introspection/libdispatch.dylib:arm64+0x145b)
>    #8 0x105f1a04f in _dispatch_main_queue_callback_4CF (/usr/lib/system/introspection/libdispatch.dylib:arm64+0x604f)
>    #9 0x181d43f1f in <redacted> (/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation:arm64+0xe9f1f)
>    #10 0x181d41afb in <redacted> (/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation:arm64+0xe7afb)
>    #11 0x181c622d7 in CFRunLoopRunSpecific (/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation:arm64+0x82d7)
>    #12 0x183af3f83 in GSEventRunModal (/System/Library/PrivateFrameworks/GraphicsServices.framework/GraphicsServices:arm64+0xaf83)
>    #13 0x18b20e87f in UIApplicationMain (/System/Library/Frameworks/UIKit.framework/UIKit:arm64+0x7387f)
>    #14 0x100122dc3 in main (<redacted>:arm64+0x1000aadc3)
>    #15 0x18178656b in <redacted> (/usr/lib/system/libdyld.dylib:arm64+0x156b)
> 
> Address 0x00014d3ea224 is located in stack of thread T0 at offset 36 in frame
>    #0 0x10045cd57 in function signature specialization <Arg[0] = Owned To Guaranteed, Arg[1] = Owned To Guaranteed, Arg[2] = Owned To Guaranteed and Exploded> of closure #1 () -> () in <redacted>.imageDownloaded(<redacted>.DownloadImageRequest, index: Swift.Int) -> () (<redacted>:arm64+0x1003e4d57)
> 
>  This frame has 4 object(s):
>    [32, 36) '' <== Memory access at offset 36 overflows this variable
>    [48, 208) ''
>    [272, 276) ''
>    [288, 448) ''
> HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
>      (longjmp and C++ exceptions *are* supported)
> SUMMARY: AddressSanitizer: stack-buffer-overflow (<redacted>:arm64+0x100e41a83) in <redacted>::addImage(void const*, int, int, int, float const*, float const*)
> Shadow bytes around the buggy address:
>  0x0001302dd3f0: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
>  0x0001302dd400: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
>  0x0001302dd410: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
>  0x0001302dd420: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
>  0x0001302dd430: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
> =>0x0001302dd440: f1 f1 f1 f1[04]f2 00 00 00 00 00 00 00 00 00 00
>  0x0001302dd450: 00 00 00 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 f2
>  0x0001302dd460: f2 f2 04 f2 00 00 00 00 00 00 00 00 00 00 00 00
>  0x0001302dd470: 00 00 00 00 00 00 00 00 f3 f3 f3 f3 f3 f3 f3 f3
>  0x0001302dd480: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
>  0x0001302dd490: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
> Shadow byte legend (one shadow byte represents 8 application bytes):
>  Addressable:           00
>  Partially addressable: 01 02 03 04 05 06 07 
>  Heap left redzone:       fa
>  Freed heap region:       fd
>  Stack left redzone:      f1
>  Stack mid redzone:       f2
>  Stack right redzone:     f3
>  Stack after return:      f5
>  Stack use after scope:   f8
>  Global redzone:          f9
>  Global init order:       f6
>  Poisoned by user:        f7
>  Container overflow:      fc
>  Array cookie:            ac
>  Intra object redzone:    bb
>  ASan internal:           fe
>  Left alloca redzone:     ca
>  Right alloca redzone:    cb
> 
> 
> 
> So, how do I properly go back and forth between C and Swift code with pointers to arrays of floats?
> 
> Thanks,
> 
> -- 
> Rick Mann
> rmann at latencyzero.com
> 
> 
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org
> https://lists.swift.org/mailman/listinfo/swift-users



More information about the swift-users mailing list