Crash on Linux when creating a Date from Calendar.date(from: DateComponents)

Hello everyone, I have the following code to decode a Date from wire data, it works fine on macOS but it crashes code on linux (specifically ubuntu 22). The crash occurs when I am constructing a Date from DateComponents via Calendar.date(from:). The crash is a Code 4 SIGTRAP. Am I doing anything wrong?

I've added example code to reproduce the crash below:

import struct Foundation.Calendar
import struct Foundation.Date
import struct Foundation.DateComponents
import struct Foundation.TimeZone
import NIOCore

enum Constants {
    static let TNS_HAS_REGION_ID: UInt8 = 0x80
    static let TZ_HOUR_OFFSET: UInt8 = 20
    static let TZ_MINUTE_OFFSET: UInt8 = 60
}

public extension Date {
    init(
        from buffer: inout ByteBuffer
    ) throws {
        let length = buffer.readableBytes
        guard
            length >= 7,
            let firstSevenBytes = buffer.readBytes(length: 7)
        else {
            // throw OracleDecodingError.Code.missingData
            fatalError()
        }
        let year = (Int(firstSevenBytes[0]) - 100) * 100 +
            Int(firstSevenBytes[1]) - 100
        let month = Int(firstSevenBytes[2])
        let day = Int(firstSevenBytes[3])
        let hour = Int(firstSevenBytes[4]) - 1
        let minute = Int(firstSevenBytes[5]) - 1
        let second = Int(firstSevenBytes[6]) - 1
        var calendar = Calendar(identifier: .gregorian)
        calendar.timeZone = TimeZone(secondsFromGMT: 0)!
        var components = DateComponents(
            calendar: calendar,
            year: year,
            month: month,
            day: day,
            hour: hour,
            minute: minute,
            second: second
        )
        if length >= 11, let value = buffer.readInteger(
            endianness: .big, as: UInt32.self
        ) {
            let fsecond = Double(value) / 1000.0
            components.nanosecond = Int(fsecond * 1_000_000_000)
        }
        let (byte11, byte12) = buffer
            .readMultipleIntegers(as: (UInt8, UInt8).self) ?? (0, 0)

        if length > 11, byte11 != 0, byte12 != 0 {
            if byte11 & Constants.TNS_HAS_REGION_ID != 0 {
                // Named time zones are not supported
                // throw OracleDecodingError.Code.failure
                fatalError()
            }
            let tzHour = Int(byte11 - Constants.TZ_HOUR_OFFSET)
            let tzMinute = Int(byte12 - Constants.TZ_MINUTE_OFFSET)
            if tzHour != 0 || tzMinute != 0 {
                guard let timeZone = TimeZone(
                    secondsFromGMT: tzHour * 3600 + tzMinute * 60
                ) else {
                    // throw OracleDecodingError.Code.failure
                    fatalError()
                }
                components.timeZone = timeZone
            }
        }
        guard let value = calendar.date(from: components) else {
            // .                        ^ crash happens here
            // throw OracleDecodingError.Code.failure
            fatalError()
        }
        self = value
    }
}

Example:

var buffer = ByteBuffer(bytes: [120, 123, 8, 27, 20, 31, 49, 36, 58, 250, 136, 20, 60])
print(try? Date(from: &buffer))

Call Stack:

function signature specialization <Arg[4] = Dead> of Foundation.NSCalendar._convert(_: Swift.Optional<Swift.Int>, type: Swift.String, vector: inout Swift.Array<Swift.Int32>, compDesc: inout Swift.Array<Swift.Int8>) -> () (@function signature specialization <Arg[4] = Dead> of Foundation.NSCalendar._convert(_: Swift.Optional<Swift.Int>, type: Swift.String, vector: inout Swift.Array<Swift.Int32>, compDesc: inout Swift.Array<Swift.Int8>) -> ():22)
function signature specialization <Arg[1] = Dead> of Foundation.NSCalendar._convert(Foundation.DateComponents) -> (Swift.Array<Swift.Int32>, Swift.Array<Swift.Int8>) (@function signature specialization <Arg[1] = Dead> of Foundation.NSCalendar._convert(Foundation.DateComponents) -> (Swift.Array<Swift.Int32>, Swift.Array<Swift.Int8>):171)
Foundation.NSCalendar.date(from: Foundation.DateComponents) -> Swift.Optional<Foundation.Date> (@Foundation.NSCalendar.date(from: Foundation.DateComponents) -> Swift.Optional<Foundation.Date>:13)
partial apply forwarder for closure #1 (Foundation.NSCalendar) -> Swift.Optional<Foundation.Date> in Foundation.Calendar.date(from: Foundation.DateComponents) -> Swift.Optional<Foundation.Date> (@partial apply forwarder for closure #1 (Foundation.NSCalendar) -> Swift.Optional<Foundation.Date> in Foundation.Calendar.date(from: Foundation.DateComponents) -> Swift.Optional<Foundation.Date>:15)
Foundation._MutableHandle.map<τ_0_0>((τ_0_0) throws -> τ_1_0) throws -> τ_1_0 (@Foundation._MutableHandle.map<τ_0_0>((τ_0_0) throws -> τ_1_0) throws -> τ_1_0:25)
Foundation.Calendar.date(from: Foundation.DateComponents) -> Swift.Optional<Foundation.Date> (@Foundation.Calendar.date(from: Foundation.DateComponents) -> Swift.Optional<Foundation.Date>:20)
Foundation.Date.init(from: inout NIOCore.ByteBuffer) throws -> Foundation.Date (/workspaces/test1/DateDecoding.swift:70)

After further investigation I've come to the conclusion that the crash occurs due to how I am calculating the nanoseconds.I've noticed that the calculation I was doing doesn't work reliably and sometimes calculates nanosecond to be more than a second, which causes the crash on linux.

Here is a simplified example:

let year = 2023
let month = 8
let day = 27
let hour = 19
let minute = 30
let second = 48
let fsecond = 607_845
let fractional = Double(fsecond) / pow(10.0, Double(String(fsecond).count))
let nanosecond = Int(fractional * 1_000_000_000) // * 10 // to trigger the crash
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
let components = DateComponents(
    calendar: calendar,
    year: year,
    month: month,
    day: day,
    hour: hour,
    minute: minute,
    second: second,
    nanosecond: nanosecond
)
let value = calendar.date(from: components)

You can create a bug report on GitHub - apple/swift-corelibs-foundation: The Foundation Project, providing core utilities, internationalization, and OS independence

Whether it gets fixed or not is another matter since most work in that area is going on new Foundation

1 Like