简体   繁体   中英

Occasional crashes while read from NSDataAsset

Some of my users reported a crash. I'm not sure if I do the right think, so I show the code and the part of the crash report with the error description and maybe someone has a helpful hint for me.

UseCase: A file in the Asset Catalog contains the coordinates of several border lines of countries. There are also some strings in it. This code fragment reads the file and stores the converted data into variables.

SOMETIMES the app crashes (I receive 1 or 2 reports a week) at the last line of the code fragment (let numberOfCountries = Int(dataReadBufferUInt64)). I have a group of beta testers and they have no problem at all. So it is a frustrating issue.

I think it has something to do HOW I read the data out of the file. Maybe I have to use another way?

Any help/comment is welcomed!

DataStructure:

The Asset is a programmatically produced file. The code fragment is dealing with the header of this file.

First element is a copyright string with some informations of the version of the file, creation date etc.

The string has two elements: 1) the length of the string as a UInt16 value (range 0 ... UInt16.max) 2) the string elements (if length > 0), each "character" as one UInt16 value

Second element is the number of the countries in that file. It is a UInt64 value.

The rest of the data structure is quite complex, but is not relevant here, as the file is read sequential and IF it crashes, it crashes when reading / converting the UInt64.

There is only one version of the file "out in the wild". Every app reads this file every time the UserInterface gets build up. This is why I do not understand the OCCASIONAL crashes ...

The code fragment:

// calculate the size of a record, we use "size" as this is the number of bytes used to store on record
// other possibilities:
// .stride = number of bytes used to store one record and the added nul bytes to align to next memory bounds
// .alignment = number of bytes of alignment bounds
let sizeUInt16 : UInt64 =               UInt64(MemoryLayout<UInt16>.size)
let sizeUInt64 : UInt64 =               UInt64(MemoryLayout<UInt64>.size)



// get the data out of the asset catalog
if let countryBorderLineData = NSDataAsset(name: "CountryBorderLine data", bundle: Bundle.main)?.data {

    // the read buffers, one for each expected data type
    var dataReadBufferUInt16 : UInt16 = 0
    var dataReadBufferUInt64 : UInt64 = 0


    // read the string with the entry comment
    // read the length of the string
    (countryBorderLineData as NSData).getBytes(&dataReadBufferUInt16, range: NSRange(location: nextLocation,
                                                                      length: Int(sizeUInt16) ))
    // advance the pointer
    nextLocation += Int(sizeUInt16)

    // take the number of items we should read
    var numberOfItemsToRead : Int = Int(dataReadBufferUInt16)

    // check if this is not an empty string
    if numberOfItemsToRead > 0 {

       // target buffer of the string
       var UTF16Array : [UInt16] = []

       // loop to read all content
       for _ in 0 ..< numberOfItemsToRead {

            // read next string element
            (countryBorderLineData as NSData).getBytes(&dataReadBufferUInt16, range: NSRange(location: nextLocation,
                                                                              length: Int(sizeUInt16) ))
            // advance the pointer
            nextLocation += Int(sizeUInt16)

            // append read string element to the array

            UTF16Array.append(dataReadBufferUInt16)
        }

        // convert the read array into a string
        let resultString = String(utf16CodeUnits: UTF16Array, count: UTF16Array.count)

    }

    // read the number of countries
    (countryBorderLineData as NSData).getBytes(&dataReadBufferUInt64, range: NSRange(location: nextLocation,
                                                                                 length: Int(sizeUInt64) ))
    // advance the pointer
    nextLocation += Int(sizeUInt64)

    // This line SOMETIMES crashes (see crash subset of crash report)
    let numberOfCountries = Int(dataReadBufferUInt64)

    ...

}

This part of the crash report shows the error

Date/Time:           2019-08-28 22:00:06.5042 +0200
Launch Time:         2019-08-28 22:00:02.2638 +0200
OS Version:          iPhone OS 12.4 (16G77)
Baseband Version:    1.06.02
Report Version:      104

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x8000000000000010
VM Region Info: 0x8000000000000010 is not in any region.  Bytes after     previous region: 9223372025580486673  
  REGION TYPE                      START - END             [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
  MALLOC_NANO            0000000280000000-00000002a0000000 [512.0M] rw-/rwx SM=PRV  
--->  
  UNUSED SPACE AT END

Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [22974]
Triggered by Thread:  3

All methods of this class are called within the following GCD queue:

let myQueueForBorderLines : DispatchQueue = DispatchQueue(
label: "appName.myQueueForBorderLines", qos: .userInitiated)

The used data structures etc. are only read and managed by this class methods, so I think this is not a multithreading issue.

UPDATE

With knowing that numberOfBytesToRead in the old code cannot be the cause of the issue, I can just show you the recommended usage of Data as for now.

let sizeUInt16 = MemoryLayout<UInt16>.size
let sizeUInt64 = MemoryLayout<UInt64>.size

// get the data out of the asset catalog
if let countryBorderLineData = NSDataAsset(name: "CountryBorderLine data", bundle: Bundle.main)?.data {
    var nextLocation = 0

    // the read buffers, one for each expected data type
    var dataReadBufferUInt16: UInt16 = 0
    var dataReadBufferUInt64: UInt64 = 0

    // read the string with the entry comment
    // read the length of the string
    _ = withUnsafeMutableBytes(of: &dataReadBufferUInt16) {bufPtr in
        countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
    }
    // advance the pointer
    nextLocation += Int(sizeUInt16)

    // take the number of items we should read
    let numberOfItemsToRead = Int(dataReadBufferUInt16)

    // check if this is not an empty string
    if numberOfItemsToRead > 0 {

        // target buffer of the string
        var utf16Array: [UInt16] = Array(repeating: 0, count: numberOfItemsToRead)

        utf16Array.withUnsafeMutableBufferPointer {bufPtr in
            countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
        }
        // advance the pointer
        nextLocation += numberOfItemsToRead * sizeUInt16

        // convert the read array into a string
        let resultString = String(utf16CodeUnits: utf16Array, count: utf16Array.count)
        print(resultString)
    }

    // read the number of countries
    _ = withUnsafeMutableBytes(of: &dataReadBufferUInt64) {bufPtr in
        countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
    }
    // advance the pointer
    nextLocation += sizeUInt64

    // This line SOMETIMES crashes (see crash subset of crash report)
    let numberOfCountries = Int(dataReadBufferUInt64)

    //...

}

The implementation of Swift.Data has changed recently, so it might be causing some issues, but the possibility is not high (little more than very unlikely ).

If your code runs in the multithreaded context, that may lead SOMETIMES crashes .

Anyway, when you show more context, I will check it and update my answer again.


OLD ANSWER

It depends on how your is organized (you should better show the spec of the NSDataAsset), but your code consumes numberOfBytesToRead * sizeUInt16 bytes, with this loop:

       // loop to read all content
       for _ in 0 ..< numberOfBytesToRead {

            //...

            // advance the pointer
            nextLocation += Int(sizeUInt16)

            //...
        }

After this loop, nextLocation may be pointing some unknown position, which may: - be exceeding the valid range of your countryBorderLineData - cause Int(dataReadBufferUInt64) to overflow - ...


And looping through each UTF-16 code point is not an efficient way to read UTF-16 String.

I would re-write your code as:

let sizeUInt16 = MemoryLayout<UInt16>.size
let sizeUInt64 = MemoryLayout<UInt64>.size

if let countryBorderLineData = data {
    var nextLocation = 0

    // the read buffers, one for each expected data type
    var dataReadBufferUInt16: UInt16 = 0
    var dataReadBufferUInt64: UInt64 = 0

    // read the string with the entry comment
    // read the length of the string
    _ = withUnsafeMutableBytes(of: &dataReadBufferUInt16) {bufPtr in
        countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
    }
    // advance the pointer
    nextLocation += sizeUInt16

    // take the number of bytes we should read
    let numberOfBytesToRead = Int(dataReadBufferUInt16)

    // check if this is not an empty string
    if numberOfBytesToRead > 0 {
        assert(numberOfBytesToRead.isMultiple(of: sizeUInt16))

        // target buffer of the string
        var utf16Array: [UInt16] = Array(repeating: 0, count: numberOfBytesToRead/sizeUInt16)

        utf16Array.withUnsafeMutableBufferPointer {bufPtr in
            countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
        }
        // advance the pointer
        nextLocation += numberOfBytesToRead

        // convert the read array into a string
        let resultString = String(utf16CodeUnits: utf16Array, count: utf16Array.count)
        print(resultString)
    }

    // read the number of countries
    _ = withUnsafeMutableBytes(of: &dataReadBufferUInt64) {bufPtr in
        countryBorderLineData.copyBytes(to: bufPtr, from: nextLocation...)
    }
    // advance the pointer
    nextLocation += sizeUInt64

    // This line SOMETIMES crashes (see crash subset of crash report)
    let numberOfCountries = Int(dataReadBufferUInt64)
    //...

}

My guess might be wrong and the code above would not solve your issue, in such cases, please show more info about your data and I can correct my answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM