iosswiftcoremidi

crash with MIDIPacketNext


I wrote an app ten years ago in Objective-C with PGMidi, which is a helper class for Core MIDI. PGMidi's input functions look like this:

static void PGMIDIReadProc(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) {
    PGMidiSource *source = arc_cast<PGMidiSource>(srcConnRefCon);
    [source midiRead:pktlist fromPort:source.name];
}

- (void) midiRead:(const MIDIPacketList *)pktlist fromPort:(NSString *)port {
    NSArray *delegates = self.delegates;
    for (NSValue *delegatePtr in delegates) {
        id<PGMidiSourceDelegate> delegate = (id<PGMidiSourceDelegate>)[delegatePtr pointerValue];
        [delegate midiSource:self midiReceived:pktlist fromPort:port];
    }
}

And that passes data to my app functions:

static NSMutableArray *packetToArray(const MIDIPacket *packet) {
    NSMutableArray *values = [[NSMutableArray alloc] initWithCapacity:3];
    NSNumber *value;
    for (int j=0; j<packet->length; j++) {
        value = [[NSNumber alloc] initWithUnsignedInt:packet->data[j]];
        [values addObject:value];
    }
    return values;
}

- (void)midiSource:(PGMidiSource *)midi midiReceived:(const MIDIPacketList *)packetList fromPort:(NSString *)port {
    const MIDIPacket *packet = &packetList->packet[0];
    for (int i=0; i<packetList->numPackets; ++i) {
        NSDictionary *data = [[NSDictionary alloc] initWithObjectsAndKeys:
            packetToArray(packet), @"values",
            port, @"port",
        nil];
        [self performSelectorOnMainThread:@selector(receiveMidiData:) withObject:data waitUntilDone:FALSE];
    
        packet = MIDIPacketNext(packet);
    }
}

This has always worked fine. But earlier this year I converted my app to Swift. I left the PGMidi class alone, but rewrote my app functions to this:

func packetToArray(_ packet: MIDIPacket) -> [Int] {
    var values = [Int]()
    let packetMirror = Mirror(reflecting: packet.data)
    let packetValues = packetMirror.children.map({ $0.value as? UInt8 })
    for i in 0..<Int(packet.length) {
        if (packetValues.count > i), let value = packetValues[i] {
            values.append(Int(value))
        }
    }
    return values
}

@objc func midiSource(_ midi: PGMidiSource, midiReceived packetList: UnsafePointer<MIDIPacketList>, fromPort port: String) {
    var packet = packetList.pointee.packet // this gets the first packet
    for _ in 0..<packetList.pointee.numPackets {
        let packetArray = App.packetToArray(packet)
        let data: [AnyHashable : Any] = [
            "values" : packetArray,
            "port" : port
        ]
        self.performSelector(onMainThread: #selector(receiveMidiData(_:)), with: data, waitUntilDone: false)

        packet = MIDIPacketNext(&packet).pointee
    }
}

This mostly works, but I'm getting some crashes on the MIDIPacketNext line. This isn't consistent and seems to happen when a lot of data is coming in, like from the expression wheel on a keyboard. One crash log included this:

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x000000016b78914c
VM Region Info: 0x16b78914c is not in any region. Bytes after previous region: 37197 Bytes before following region: 360050356
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
Stack 16b6f8000-16b780000 [ 544K] rw-/rwx SM=PRV thread 6
---> GAP OF 0x15768000 BYTES
unused shlib __TEXT 180ee8000-180f2c000 [ 272K] r-x/r-x SM=COW ... this process

Another was slightly different, but pointed to the same line of code:

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Subtype: KERN_PROTECTION_FAILURE at 0x000000016beb46cc
VM Region Info: 0x16beb46cc is in 0x16beb4000-0x16beb8000;  bytes after start: 1740  bytes before end: 14643
      REGION TYPE                 START - END      [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      Stack                    16be2c000-16beb4000 [  544K] rw-/rwx SM=PRV  thread 5
--->  STACK GUARD              16beb4000-16beb8000 [   16K] ---/rwx SM=NUL  ... for thread 9
      Stack                    16beb8000-16bf40000 [  544K] rw-/rwx SM=PRV  thread 9

Should I be accessing the packets differently somehow to avoid this, or is there anything I can do to detect the error situation and avoid the crash?


Solution

  • The comment from Eugene Dudnyk led me to a solution. I rewrote my midiSource delegate function as follows, with the UnsafePointer extension added:

    @objc func midiSource(_ midi: PGMidiSource, midiReceived packetList: UnsafePointer<MIDIPacketList>, fromPort port: String) {
        let packetList = packetList.loadUnaligned(as: MIDIPacket.self, count: Int(packetList.pointee.numPackets))
        for packet in packetList {
            let packetArray = App.packetToArray(packet)
            let data: [AnyHashable : Any] = [
                "values" : packetArray,
                "port" : port
            ]
            self.performSelector(onMainThread: #selector(receiveMidiData(_:)), with: data, waitUntilDone: false)
        }
    }
    
    extension UnsafePointer {
        func loadUnaligned<T>(as: T.Type, count: Int) -> [T] {
            assert(_isPOD(T.self)) // relies on the type being POD (no refcounting or other management)
            let buffer = UnsafeMutablePointer<T>.allocate(capacity: count)
            defer { buffer.deallocate() }
            memcpy(buffer, self, MemoryLayout<T>.size * count)
            return (0..<count).map({ index in buffer.advanced(by: index).pointee })
        }
    }
    

    No more crashes!